Closed rohitiscancerian closed 4 years ago
Following is my config . Swashbuckle.Odata v 3.4.0
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, GlobalConfiguration.Configuration).Configure(odataConfig => { odataConfig.IncludeNavigationProperties(); odataConfig.EnableSwaggerRequestCaching(); }));
I get the same exception once I add a base controller an inherit from that instead of directly from ODataController.
public abstract class DefaultAccessODataBaseController<TEntity, TKey> : ODataController where TEntity : class
{
protected SwaggerTestDbContext _dbContext;
protected abstract Expression<Func<TEntity, TKey>> KeyProperty { get; }
public DefaultAccessODataBaseController(SwaggerTestDbContext dbContext)
: base()
{
_dbContext = dbContext;
}
[EnableQuery(PageSize = 20, HandleNullPropagation = System.Web.OData.Query.HandleNullPropagationOption.False, AllowedQueryOptions = System.Web.OData.Query.AllowedQueryOptions.All)]
// HandleNullPropagationOption.False is a workaround for issue, which should have been fixed in EFCore 2.0.0-preview1:
// https://github.com/aspnet/EntityFramework/issues/9169
[ODataRoute]
public IQueryable<TEntity> Get()
{
return _dbContext.Set<TEntity>().AsNoTracking().AsQueryable();
}
[EnableQuery(PageSize = 20, HandleNullPropagation = System.Web.OData.Query.HandleNullPropagationOption.False, AllowedQueryOptions = System.Web.OData.Query.AllowedQueryOptions.All)]
// HandleNullPropagationOption.False is a workaround for issue, which should have been fixed in EFCore 2.0.0-preview1:
// https://github.com/aspnet/EntityFramework/issues/9169
public SingleResult<TEntity> Get([FromODataUri] TKey key)
{
var filterByKey = GetFilterByKeyExpression(key);
var entity = _dbContext.Set<TEntity>().AsNoTracking().Where(filterByKey);
return SingleResult.Create(entity);
}
// PUT: odata/Entity(D35E6C2C-1548-45E4-B686-27AEFB387BF1)
public virtual async Task<IHttpActionResult> Put([FromODataUri] TKey key, Delta<TEntity> patch)
{
var entity = await _dbContext.Set<TEntity>().AsTracking().FirstOrDefaultAsync(GetFilterByKeyExpression(key));
if (entity == null)
{
return NotFound();
}
patch.Put(entity);
await ApplyRulesBeforeValidatingAndSavingAsync(entity);
Validate(entity);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
SetLastEditedAtAndLastEditedBy(entity);
_dbContext.Update(entity);
await _dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await EntityExistsAsync(key))
{
return NotFound();
}
else
{
throw;
}
}
await OnAfterSave(entity);
return Updated(entity);
}
// POST: odata/Entity
public virtual async Task<IHttpActionResult> Post(TEntity entity)
{
await ApplyRulesBeforeValidatingAndSavingAsync(entity);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
SetLastEditedAtAndLastEditedBy(entity);
_dbContext.Add(entity);
await _dbContext.SaveChangesAsync();
}
catch (DbUpdateException)
{
var key = GetKeyValue(entity, KeyProperty);
if (await EntityExistsAsync(key))
{
return Conflict();
}
else
{
throw;
}
}
await OnAfterSave(entity);
return Created(entity);
}
// PATCH: odata/Entity(D35E6C2C-1548-45E4-B686-27AEFB387BF1)
[AcceptVerbs("PATCH", "MERGE")]
public virtual async Task<IHttpActionResult> Patch([FromODataUri] TKey key, Delta<TEntity> patch)
{
var entity = await _dbContext.Set<TEntity>().AsTracking().FirstOrDefaultAsync(GetFilterByKeyExpression(key));
if (entity == null)
{
return NotFound();
}
patch.Patch(entity);
await ApplyRulesBeforeValidatingAndSavingAsync(entity);
Validate(entity);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
SetLastEditedAtAndLastEditedBy(entity);
_dbContext.Update(entity);
await _dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await EntityExistsAsync(key))
{
return NotFound();
}
else
{
throw;
}
}
await OnAfterSave(entity);
return Updated(entity);
}
// DELETE: odata/Entity(D35E6C2C-1548-45E4-B686-27AEFB387BF1)
public virtual async Task<IHttpActionResult> Delete([FromODataUri] TKey key)
{
var entity = await _dbContext.Set<TEntity>().FindAsync(key);
try
{
await ValidateDeleteEntityAsync(entity);
}
catch (Exception ex)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new ODataError()
{
ErrorCode = "13456",
Message = ex.Message
}));
}
if (entity == null)
{
return NotFound();
}
_dbContext.Remove(entity);
await _dbContext.SaveChangesAsync();
await OnAfterDelete(entity);
return StatusCode(HttpStatusCode.NoContent);
}
protected async Task<bool> EntityExistsAsync(TKey key)
{
return await _dbContext.Set<TEntity>().AnyAsync(GetFilterByKeyExpression(key));
}
protected TKey GetKeyValue(TEntity entity, Expression<Func<TEntity, TKey>> propertyLambda)
{
PropertyInfo propInfo = GetKeyPropertyInfo(propertyLambda);
TKey keyValue = (TKey)propInfo.GetValue(entity);
return keyValue;
}
protected PropertyInfo GetKeyPropertyInfo(Expression<Func<TEntity, TKey>> propertyLambda)
{
Type type = typeof(TEntity);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.", propertyLambda.ToString()));
if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format("Expresion '{0}' refers to a property that is not from type {1}.", propertyLambda.ToString(), type));
return propInfo;
}
protected Expression<Func<TEntity, bool>> GetFilterByKeyExpression(TKey keyValue)
{
var propInfo = GetKeyPropertyInfo(KeyProperty);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var lambda = Expression.Lambda<Func<TEntity, bool>>(
Expression.Equal(
Expression.PropertyOrField(parameter, propInfo.Name),
Expression.Constant(keyValue)
),
parameter
);
return lambda;
}
private void SetLastEditedAtAndLastEditedBy(TEntity entity)
{
var lastEditedAtPropertyInfo = entity.GetType().GetProperty(nameof(Customer.LastEditedAt));
lastEditedAtPropertyInfo.SetValue(entity, DateTimeOffset.UtcNow);
var lastEditedByPropertyInfo = entity.GetType().GetProperty(nameof(Customer.LastEditedBy));
lastEditedByPropertyInfo.SetValue(entity, RequestContext.Principal.Identity.Name);
}
protected virtual async Task ApplyRulesBeforeValidatingAndSavingAsync(TEntity entity)
{
// Can be overridden in subclass.
await Task.CompletedTask;
}
/// <summary>
/// Override to throw a descriptive exception if the given <paramref name="entity"/> cannot be deleted.
/// </summary>
/// <param name="entity">Entity to be deleted</param>
protected virtual async Task ValidateDeleteEntityAsync(TEntity entity)
{
await Task.CompletedTask;
}
protected virtual async Task OnAfterSave(TEntity entity)
{
await Task.CompletedTask;
}
protected virtual async Task OnAfterDelete(TEntity entity)
{
// Can be overridden in subclass.
await Task.CompletedTask;
}
}
And then a controller to represent an entity:
public class CustomersController : DefaultAccessODataBaseController<Customer, Guid>
{
protected override Expression<Func<Customer, Guid>> KeyProperty => c => c.CustomerId;
public CustomersController(SwaggerTestDbContext dbContext)
: base(dbContext)
{
}
[EnableQuery]
[ODataRoute]
public IQueryable<CustomerLicense> GetCustomerLicenses([FromODataUri] Guid key)
{
var customerLicenses = _dbContext.CustomerLicenses.Where(cl => cl.CustomerId == key).AsNoTracking();
return customerLicenses;
}
protected override async Task ApplyRulesBeforeValidatingAndSavingAsync(Customer entity)
{
await base.ApplyRulesBeforeValidatingAndSavingAsync(entity);
if (entity.CustomerNo <= 0)
{
entity.CustomerNo = _dbContext.Customers.Max(c => c.CustomerNo) + 1;
}
}
protected override async Task ValidateDeleteEntityAsync(Customer entity)
{
await ValidateNoLicenseReferencesAsync(entity);
}
private async Task ValidateNoLicenseReferencesAsync(Customer entity)
{
int licenses = 0;
if (entity == null)
{
// Default handling in base controller delete method. Don't throw exception for this.
}
if (entity.CustomerLicenses == null)
{
licenses = _dbContext.CustomerLicenses.Where(cl => cl.CustomerId == entity.CustomerId).Count();
}
else
{
licenses = entity.CustomerLicenses.Count;
}
if (licenses > 0)
{
throw new InvalidOperationException($"Customer is referenced by {licenses} licenses and cannot be deleted.");
}
await Task.CompletedTask;
}
}
Regarding my previous comment, I pulled down the sources and debugged the ODataSwaggerProvider.CreatePathItem()
method. It throws NullReferenceException
because I don't have a ConflictingActionsResolver
configured.
Once I uncomment the default ResolveConflictingActions
line in SwaggerConfig.cs it runs successfully:
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
It would be better if an exception with a more descriptive message was thrown.
Using the assembly from my proposed PRs #170 and #171, I get this Exception message instead of the one in the initial comment of this issue:
500 : {"$id":"1","Message":"An error has occurred.","ExceptionMessage":"ResolveConflictingActions is not configured for Swagger.","ExceptionType":"System.InvalidOperationException","StackTrace":" at Swashbuckle.OData.ODataSwaggerProvider.CreatePathItem(IEnumerable1 apiDescriptions, SchemaRegistry schemaRegistry) in C:\\GitHub\\eloekset\\Swashbuckle.OData\\Swashbuckle.OData\\ODataSwaggerProvider.cs:line 155\r\n at Swashbuckle.OData.ODataSwaggerProvider.<>c__DisplayClass5_0.<GenerateSwagger>b__3(IGrouping
2 group) in C:\GitHub\eloekset\Swashbuckle.OData\Swashbuckle.OData\ODataSwaggerProvider.cs:line 83\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func
2 keySelector, Func2 elementSelector, IEqualityComparer
1 comparer)\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func
2 keySelector, Func`2 elementSelector)\r\n at Swashbuckle.OData.ODataSwaggerProvider.GenerateSwagger(String rootUrl, String apiVersion) in C:\GitHub\eloekset\Swashbuckle.OData\Swashbuckle.OData\ODataSwaggerProvider.cs:line 79\r\n at Swashbuckle.OData.ODataSwaggerProvider.GetSwagger(String rootUrl, String apiVersion) in C:\GitHub\eloekset\Swashbuckle.OData\Swashbuckle.OData\ODataSwaggerProvider.cs:line 56\r\n at Swashbuckle.Application.SwaggerDocsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.HttpServer.
500 : {"Message":"An error has occurred.","ExceptionMessage":"Object reference not set to an instance of an object.","ExceptionType":"System.NullReferenceException","StackTrace":" at Swashbuckle.OData.ODataSwaggerProvider.CreatePathItem(IEnumerableb 1()\r\n at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEndAsync[TResult](ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Actiond__0.MoveNext()"} http://localhost:61797/swagger/docs/v1
1 apiDescriptions, SchemaRegistry schemaRegistry) in C:\\Users\\rbeauchamp\\Documents\\GitHub\\Swashbuckle.OData\\Swashbuckle.OData\\ODataSwaggerProvider.cs:line 155\r\n at Swashbuckle.OData.ODataSwaggerProvider.<>c__DisplayClass5_0.<GenerateSwagger>b__3(IGrouping
2 group) in C:\Users\rbeauchamp\Documents\GitHub\Swashbuckle.OData\Swashbuckle.OData\ODataSwaggerProvider.cs:line 83\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func
2 keySelector, Func2 elementSelector, IEqualityComparer
1 comparer)\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func
2 keySelector, Func2 elementSelector)\r\n at Swashbuckle.OData.ODataSwaggerProvider.GenerateSwagger(String rootUrl, String apiVersion) in C:\\Users\\rbeauchamp\\Documents\\GitHub\\Swashbuckle.OData\\Swashbuckle.OData\\ODataSwaggerProvider.cs:line 79\r\n at Swashbuckle.OData.ODataSwaggerProvider.<>c__DisplayClass4_0.<GetSwagger>b__1() in C:\\Users\\rbeauchamp\\Documents\\GitHub\\Swashbuckle.OData\\Swashbuckle.OData\\ODataSwaggerProvider.cs:line 50\r\n at System.Lazy
1.CreateValue()\r\n at System.Lazy1.LazyInitValue()\r\n at System.Lazy
1.get_Value()\r\n at Swashbuckle.OData.ODataSwaggerProvider.GetSwagger(String rootUrl, String apiVersion) in C:\Users\rbeauchamp\Documents\GitHub\Swashbuckle.OData\Swashbuckle.OData\ODataSwaggerProvider.cs:line 53\r\n at Swashbuckle.Application.SwaggerDocsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.Tracing.Tracers.RequestMessageHandlerTracer.<>cDisplayClass4.1 beginTrace, Func
1 execute, Action2 endTrace, Action
1 errorTrace)\r\n at System.Web.Http.Tracing.Tracers.RequestMessageHandlerTracer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.HttpServer.