Skip to content
Sergey Tregub edited this page Oct 11, 2018 · 23 revisions

Welcome to the AspNet-WebApi wiki!

Cross-Origin Resource Sharing (CORS)

Quote from an MDN article:

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application makes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, and port) than its own origin.

By default, all origins are allowed. You can set up origin by uncommenting this line in web.config. To specify more than one origin split them by semicolon.

For example:

  <appSettings>
    <add key="allowed-origins" value="http://localhost:5000; https://api.some-domain.com"/>
  </appSettings>

If you want more control you can adjust CORS-policy in App_Start/CorsConfig.cs.

More information can be found here.

Dependency Injection

Dependency Injection is a form of inversion of control that supports the dependency inversion design principle.
This project uses Autofac as an IoC Container but it can be replaced with any other if you will.

First, you should to configure the container. It can be done in RegisterServices method in App_Start/AutofacConfig.cs file.

For example:

builder.RegisterType<FileStore>().As<IFileStore>().SingleInstance();
builder.RegisterType<ImageMetadataLoader>().As<IImageMetadataLoader>();
builder.RegisterType<ImageRotationService>().AsSelf();

Second, you typically use registered types and interfaces in constructors of your controllers. One service can use another that way of course.

For more complex scenarios and examples see the official documentation.

IImageMetadataLoader MetadataLoader { get; }

public ProductsController(IImageMetadataLoader metadataLoader)
{
    MetadataLoader = metadataLoader ?? throw new ArgumentNullException(nameof(metadataLoader));
}

metadataLoader parameter will be autowired by the container.

You can read more on this in the official Autofac documentation.

Automapper

When you create a web-service it is a common case to map one object to another. For example, you may want to map entity read from DB to DTO (Data Transmission Object) to return it as a response. You can write your own code to do that mapping or use a ready-made solution. Automapper is a solution to this problem.

Quote from the official site:

AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another

The detailed information about using it in different cases you can get from an official documentation.

As an additional advantage of using Automapper, you get a segregation of mapping code from using it. Here we will focus on the most common cases and some simple examples to give you a starting point.

Automapper is already configured for you and ready to use. It is integrated with Autofac so you can inject mapper to your services or controllers, or you can use Autofac to resolve services in mapping code! Let's see a simple example of how to achieve this.

Let's say you need to return Dto.Product object from some method of your controller, but you have Model.Product in your code. First, we should create a mapping from Model.Product to Dto.Product. This can be done in Configure method in App_Start/AutomapperConfig.cs:

public static IMapper Configure(HttpConfiguration config)
{
    Action<IMapperConfigurationExpression> mapperConfigurationExp = cfg =>
    {
        cfg.ConstructServicesUsing(GetResolver(config));

        // TODO: Create mappings here
        // For more information see https://github.com/drwatson1/AspNet-WebApi/wiki#automapper

        // New line of code. Detailed configuration skipped for simplicity
        cfg.CreateMap<Model.Product, Dto.Product>();
    };

    var mapperConfiguration = new MapperConfiguration(mapperConfigurationExp);
    Mapper = mapperConfiguration.CreateMapper();

    return Mapper;
}

Second, use it in our controller:

public class ProductsController: ApiController
{
  IMapper Mapper { get; }
  IProductsRepo Repo { get; }

  public ProductsController(IMapper mapper, IProductsRepo repo)
  {
    Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
    Repo = repo ?? throw new ArgumentNullException(nameof(repo));
  }

  [HttpGet]
  public Dto.Product Get(int key)
  {
    return Mapper.Map<Dto.Product>(Repo.GetById(key))
  }

  [HttpGet]
  public IEnumerable<Dto.Product> Get()
  {
    return Repo.Get(key).Select(Mapper.Map<Dto.Product>);
  }
}

Let's see a couple of examples of how to configure mappings:

cfg.CreateMap<Model.Product, Dto.Product>()
  // Using a custom constructor
  .ConstructUsing(src => new Dto.Product(src.Id))
  // Using a custom formatting
  .ForMember(x => x.Date, o => o.ResolveUsing((src, dest, destMember, context) =>
    $"{src.Year}-{src.Month:D2}-{src.Day:D2}"))
  // Calculated value from another fields of original object
  .ForMember(x => x.Contract, o => o.ResolveUsing((src, dest, destMember, context) =>
    $"{src.Contract.Number} from {src.Contract.Date}"))
  .AfterMap((src, dest, ctx) =>
  {
    // Resolve service from DI-container
    dest.SupplierName = c.Options.CreateInstance<ISuppliersRepo>().GetById(src.SupplierId);
  });

Logging

Each server in a production environment must log information messages and errors. There are many libraries to do this. This project template use Serilog. You don't need to perform any configuration. All things are configured for you and will work without any additional configuration.

There are two ways to use logger. First, you can inject ILogger interface into any controller or service (preferred way), or, second, you can use a global object Log.Logger. You should avoid using the global object. Here are the examples:

public class ProductsContoller
{
  ILogger Logger { get; }

  public ProductsContoller(ILogger logger)
  {
    Logger = logger ?? throw new ArgumentNullException(nameof(logger));
  }

  [HttpGet]
  public Dto.Product Get(int key)
  {
    Logger.Information("Product with id='{key}' is requested", key);
    // Other code
  }
}

You can use Error, Warning and so on instead of Information in the example above. By default, Information is used as a minimum log level, but you can adjust it in this line in the web.config file.

Log files are created in %AppData%/Logs folder, specific to the current process user. For example, if your server runs by AppService user than the folder will be C:\Users\AppService\AppData\Roaming\Logs. A name of the file is the same as a service main assembly name with '.txt' extension. Log files are rotated each day and the folder stores log files for the last 30 days.

If you want you can change any configuration option in App_Start/LoggerConfig.cs file.

More information about using and configuring Serilog you can get in the official documentation.

Cache control

In most cases, you don't want to allow a browser to cache server responses. We use OWIN middleware to add cache-control header to all responses to any of GET requests. Middleware is implemented in Handlers/CacheControlMiddleware.cs file. It will be enough for most cases, but you can change it as needed.

Unhandled exceptions logging

If something went wrong and a server has crashed, we want to know what happens exactly and where it was. It is extremely helpful to log all unhandled exceptions. We do this by implementing IExceptionLogger interface and replacing a default implementation. You can see it in Handlers/ExceptionLogger.cs file.

Unhandled exceptions handler

By default WebApi 2 returns 500 Internal Server error HTTP status with the exception message and call stack in case of any exceptions. But this is not a recommended way to report errors for REST-services. Typically we want to return 404 status code if requested object not found, 403 if the action is not allowed and so on. We do this by implementing a global request filter. You can find it in Handlers/ExceptionFilter.cs file. The idea is simple. We just check whether an exception is one of well-known type and, if so, create and return appropriate HTTP status and response.

Let's see an example:

public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
    if (actionExecutedContext.Exception is HttpResponseException)
    {
        actionExecutedContext.Response = (actionExecutedContext.Exception as HttpResponseException).Response;
        return;
    }

    var responseContent = new ErrorResponse(actionExecutedContext.Exception);
    var status = HttpStatusCode.InternalServerError;
    if(actionExecutedContext.Exception is KeyNotFoundException)
    {
        status = HttpStatusCode.NotFound;
    }

    actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(status, responseContent);
}

You should define your own exceptions and process them here to get the right HTTP status code.

Response/request data formatter

Formatters are used by WebApi to deserialize requests data and to serialize responses. Generaly we want to use JSON for that, but WebApi uses XML as a default data format. Code in a App_Start/FormatterConfig.cs file removes XmlFormatter and set up some useful options for the JSON formatter.