简体   繁体   中英

C# Web API Versioning

I have spent several days trying to get C# Web API header versioning working; and got a far as it now calls a different namespace eg v1, v2 but when I try and make a request with a parameter such as an id it doesn't work, instead it just call the default web api controller method.

All code is shown below so would really apprecaite some help in understanding how to get it calling the method with the guid and not just the default empty method.

Below is the NamespaceControllerSelector that I am using. I am adding it via the WebApi.Config.cs file and adding the Replace method call as below.

config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceControllerSelector(config));

The controller selector

public class NamespaceControllerSelector : IHttpControllerSelector
    {
        //private const string NamespaceKey = "namespace";
        private const string ControllerKey = "controller";

        private readonly HttpConfiguration _configuration;
        private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
        private readonly HashSet<string> _duplicates;

        public NamespaceControllerSelector(HttpConfiguration config)
        {
            _configuration = config;
            _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
        }

        private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
        {
            var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

            // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
            // segment of the full namespace. For example:
            // MyApplication.Controllers.V1.ProductsController => "V1.Products"
            IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
            IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

            ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);

            foreach (Type t in controllerTypes)
            {
                var segments = t.Namespace.Split(Type.Delimiter);

                // For the dictionary key, strip "Controller" from the end of the type name.
                // This matches the behavior of DefaultHttpControllerSelector.
                var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

                var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName);

                // Check for duplicate keys.
                if (dictionary.Keys.Contains(key))
                {
                    _duplicates.Add(key);
                }
                else
                {
                    dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
                }
            }

            // Remove any duplicates from the dictionary, because these create ambiguous matches. 
            // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
            foreach (string s in _duplicates)
            {
                dictionary.Remove(s);
            }
            return dictionary;
        }

        // Get a value from the route data, if present.
        private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
        {
            object result = null;
            if (routeData.Values.TryGetValue(name, out result))
            {
                return (T)result;
            }
            return default(T);
        }

        public HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            IHttpRouteData routeData = request.GetRouteData();
            if (routeData == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Get the namespace and controller variables from the route data.
            string namespaceName = "v" + GetVersionFromHttpHeader(request);//GetRouteVariable<string>(routeData, NamespaceKey);
            if (namespaceName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            string controllerName = GetControllerName(request);//GetRouteVariable<string>(routeData, ControllerKey);
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Find a matching controller.
            string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName);

            HttpControllerDescriptor controllerDescriptor;
            if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
            {
                return controllerDescriptor;
            }
            else if (_duplicates.Contains(key))
            {
                throw new HttpResponseException(
                    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                    "Multiple controllers were found that match this request."));
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }

        public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
        {
            return _controllers.Value;
        }

        private string GetVersionFromHttpHeader(HttpRequestMessage request)
        {
            const string headerName = "version";

            if (request.Headers.Contains(headerName))
            {
                var versionHeader = request.Headers.GetValues(headerName).FirstOrDefault();
                if (versionHeader != null)
                {
                    return versionHeader;
                }
            }

            return string.Empty;
        }


        public virtual string GetControllerName(HttpRequestMessage request)
        {

            IHttpRouteData routeData = request.GetRouteData();
            if (routeData == null)
            {
                return null;
            }

            // Look up controller in route data
            object controllerName = null;
            routeData.Values.TryGetValue(ControllerKey, out controllerName);
            return (string)controllerName;
        }

    }

The UsersController that I am calling

namespace Api.Controllers.v1
{
    //[Authorize]
    /// <summary>
    /// User controller
    /// </summary>
    //[RoutePrefix("api")]
    public class UsersController : ApiController
    {
        /// <summary>
        /// Get all the users in the system
        /// </summary>
        /// <returns>A list of User objects</returns>
        //[Route("users")]
        [HttpGet]
        public HttpResponseMessage GetUsers()
        {
            return Request.CreateResponse(HttpStatusCode.OK, DummyUsers.GetAllUsers());
        }

        /// <summary>
        /// Get a user by there id
        /// </summary>
        /// <param name="userId">The users id</param>
        /// <returns>A User object, will return 404 (Not Found) if unable to find</returns>
        //[Route("users/{userId:guid}")]
        [HttpGet]
        public HttpResponseMessage GetUsers(Guid userId)
        {
            User user = DummyUsers.GetAllUsers().FirstOrDefault(x => x.Id == userId);
            if (user != null)
            {
                return Request.CreateResponse(HttpStatusCode.OK, user);
            }
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }

    }
}

The request I am making, as you can see I am passing the guid after api/users so wuld expect it to call the GetUsers(Guid guid) method but its just calls the GetUsers() method. Help needed?

GET http://localhost:62249/api/users/d4eb9dd9-571a-406b-9b6d-07154293c21d
Host: localhost:62249
Connection: keep-alive
Version: 1
Accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8

Many thanks Andrew

The problem was that the

GetUsers(Guid guid) 

should have been

GetUsers(Guid id)

Possibly an amateur mistake but new to this as presumed it was cleaver enough to know. Routing would seem to be a bit of a concern with some get methods with extra parameters.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM