Skip to content

MarkusEggerInc/CODEFrameworkCore

Repository files navigation

CODE Framework ASP.NET Core Services/APIs

This repository has moved to https://github.com/codeframework/CODEFrameworkServices

Create a new Project

The following provides basic steps for a service handler based project setup.

The following is a quick walk through for creating a new project. Here's what the project should look like with appropriate references applied:

Create a new Web Solution for the Service Host

The Web application acts merely as a host to the services that are created in separate projects and that perform all the relevant service and business logic.

Start by creating a new Solution level project:

  • Create a new Project
  • Create a .NET Core Web Project
  • Choose the Empty Web template

For now, leave that project - we'll come back to it in a minute.

Create Contracts .NET Standard Class Library
  • Create a new .NET Standard Class library project
  • Add Nuget Packages
    • CODE.Framework.Services.Contracts
  • Create a Service Contract
  • Create [DataContract] objects for inputs and outputs
    • BaseServiceRequest
    • BaseServiceResponse
Example:
namespace Sample.Contracts
{
    public interface ICustomerService
    {
        [Rest(Method = RestMethods.Post, Name ="Customer", Route = "{id:guid}")]
        Task<GetCustomerResponse> GetCustomer(GetCustomerRequest request);

        [Rest(Method = RestMethods.Get, Name = "", Route = "")]
        GetCustomersResponse GetCustomers();
    }


    [DataContract]
    public class GetCustomerRequest : BaseServiceRequest
    {
        [DataMember]
        public string Id { get; set; }
    }

    [DataContract]
    public class GetCustomersResponse : BaseServiceResponse
    {
        [DataMember]
        public List<Customer> CustomerList { get; set; }
    }

    [DataContract]
    public class GetCustomerResponse : BaseServiceResponse
    {
        [DataMember]
        public Customer Customer { get; set; }
    }


    // whatever business data might be exposed either as
    // individual values or business object entities
    public class Customer
    {
        public string Name { get; set; }
        public string Company { get; set; }
        public string Id { get; set; }
    }

}
Create a Service Implementation .NET Standard Class Library
  • Create a new .NET Standard Class library project
  • Add a reference to CODE.Framework.Services.Server.AspNetCore
  • Add reference to the Contract project created above
  • Implement Services that implement the contracts

Example:

namespace Sample.Services.Implementation
{
    public class CustomerService : ICustomerService
    {        

        public GetCustomersResponse GetCustomers()
        {
            return new GetCustomersResponse()
            {
                Success = true,
                CustomerList = new List<Customer>() {
                    new Customer {
                         Name = "Rick Strahl",
                        Company = "West wind"
                    },
                    new Customer {
                         Name = "Markus Egger",
                        Company = "Eps Software"
                    },
                }
            };
        }

        public async Task<GetCustomerResponse> GetCustomer(GetCustomerRequest request)
        {   
            var result = new GetCustomerResponse()
            {
                Customer = new Customer() {
                    Id = request.Id,
                    Name = "Rick Strahl",
                    Company = "West wind"
                }                
            };

            return result;
        }
    }
}

Implement Web Project to host the Service

  • Create an empty .NET Core Web site
  • Add Nuget package for CODE.Framework.Services.Server.AspNetCore
  • Add a reference to the Implementation project from above
  • Configure the Startup configuration for the Service hosting
  • Add applicationhost.json for configuration
public class Startup
{
    public IConfiguration Configuration { get; }
    
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add the Service Handler middleware
        // `config` is optional - you can configure with `applicationhost.json`
        // if you specify it overrides the config file
        services.AddServiceHandler(config =>
        {
            config.Services.Clear();                
            config.Services.AddRange(new List<ServiceHandlerConfigurationInstance>
            {
                new ServiceHandlerConfigurationInstance
                {
                    //ServiceType = typeof(UserService), // Using an explicit Type (assembly reference comes in)
                    ServiceTypeName = "Sample.Services.Implementation.UserService",
                    AssemblyName = "Sample.Services.Implementation",
                    RouteBasePath = "/api/users",
                    JsonFormatMode = JsonFormatModes.CamelCase
                },
                new ServiceHandlerConfigurationInstance
                {
                    ServiceTypeName = "Sample.Services.Implementation.CustomerService", // dynamically loaded type
                    AssemblyName = "Sample.Services.Implementation",  // framework needs to load assembly - might need .dll extension
                    RouteBasePath = "/api/customers",
                    JsonFormatMode = JsonFormatModes.ProperCase,
                    OnAuthorize = context =>
                    {
                        context.HttpContext.User = new ClaimsPrincipal(
                            new ClaimsIdentity(
                                new Claim[] {
                                    new Claim("Permission", "CanViewPage"),
                                    new Claim(ClaimTypes.Role, "Administrator"),
                                    new Claim(ClaimTypes.NameIdentifier, "Rick")},
                                "Basic"));

                        return Task.FromResult(true);
                    }
                }
            });

            config.Cors.UseCorsPolicy = true;
            config.Cors.AllowedOrigins = "*";
        });                        
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ServiceHandlerConfiguration config)
    {            
        app.UseServiceHandler();            
    }
}

The code above explicitly uses code to configure services and the service configuration. As an alternative you can use configuration settings in applicationhost.json instead:

{
  "Logging": { ... },
  "ServiceHandler": {
    "Services": [
      {
        "ServiceTypeName": "Sample.Services.Implementation.UserService",
        "AssemblyName": "Sample.Services.Implementation.dll",
        "RouteBasePath": "/api/users",
        "JsonFormatMode": "CamelCase"
      },
      {
        "ServiceTypeName": "Sample.Services.Implementation.CustomerService",
        "AssemblyName": "Sample.Services.Implementation.dll",
        "RouteBasePath": "/api/customers",
        "JsonFormatMode": "ProperCase",
        "HttpsMode": "Http"
      }
    ],
    "Cors": {
      "UseCorsPolicy": true,
      "CorsPolicyName": "ServiceHandlerCorsPolicy",
      "AllowedOrigins": "*",
      "AllowedMethods": "\"GET,POST,PUT,OPTIONS,DELETE,MOVE,COPY,TRACE,CONNECT,MKCOL\"",
      "AllowedHeaders": null,
      "AllowCredentials": true
    }
  }
}

Authentication

Authentication can be managed using the standard IPrinciple based user store. In .NET Core there is no thread specific user context, so access to the user context is a little more tricky. In our service structure you have to use a special helper to retrieve an instance of the user principal:

var principal = this.GetCurrentPrincipal();  // on Service instance

// or
var principal = UserPrincipalHelper.GetCurrentPrincipal(service);   // service instance

if (principal.Identity.IsAuthenticated)
{
    // comma delimited string
    var roles = pricipal.Identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role); 
}

Once you have the principal you can check for authentication using standard claims/user validation.

You can manage authentication in two ways:

  • Via service contract on service methods via [Rest(AuthorizationRoles="Administrators"]
  • Explicitly in code using UserPrincipleHelper to retrieve a ClaimsIdentity

Using REST Attribute AuthorizationRoles

You can configure a service method in the contract to explicitly require a set of access roles:

[Rest(Method = RestMethods.Post, Name ="Customer", Route = "{id:guid}", AuthorizationRoles = "Administratort")]
GetCustomerResponse GetCustomer(GetCustomerRequest request);

Using explicit code you can do the following:

 public GetCustomerResponse GetCustomer(GetCustomerRequest request)
{
    var user = this.GetCurrentPrincipal();
    var isValid = user.IsInRole("Administrators");
}

Warning: IPrinciple not available in CTOR

The IPrinciple user is not available in constructor code as an instance of the service is required to set the principal without a dependency on ASP.NET Core libs or dependency injection.