Wrapping IHttpActionResult - Generic solution - asp.net-mvc

I'd like to wrap IHttpActionResult because I need some extra data to be consumed by the client app.
My first approach was to create and return simple DTO, wrapping result object if succeeded:
Response DTO:
public class Response<T>
{
public string ErrorMessage { get; set; }
public bool Success { get; set; }
public string CodeStatus { get; set; }
public T Result { get; set; }
public Response(bool isSuccess, [Optional] T result, [Optional] string codeStatus, [Optional] string errorMessage)
{
Success = isSuccess;
Result = result;
CodeStatus = codeStatus;
ErrorMessage = errorMessage;
}
}
Controller:
public IHttpActionResult Get(int id)
{
return BadRequest(new Response<MyObjectClass>(false, null,"Invalid Id",400));
...
return Ok(new Response<MyObjectClass>(true, result);
}
I've found it very ineffective way to deal with wrapping. I dont find it very elegant way. I've tried to figured out some generic solution and ended up with the following:
Example Controller Action:
public IHttpActionResult GetById(int id)
{
var result = _someService.Get(id);
if (result == null)
return NotFound().WithError("Invalid Id");
return Ok().WithSuccess(result);
}
This still returns Response DTO.
I've wrapped IHttpActionResult to deal with creating Response DTO:
public class HttpActionResult : IHttpActionResult
{
private readonly string _errorMessage;
private readonly IHttpActionResult _innerResult;
private readonly object _result;
private readonly bool _isSuccess;
public HttpActionResult(IHttpActionResult inner, bool isSuccess, object result,string errorMessage)
{
_errorMessage = errorMessage;
_innerResult = inner;
_result = result;
_isSuccess = isSuccess;
}
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await _innerResult.ExecuteAsync(cancellationToken);
response.Content = new ObjectContent(typeof(Response), new Response(_isSuccess, _result, ((int)response.StatusCode).ToString(), _errorMessage), new JsonMediaTypeFormatter());
return response;
}
}
Finally I've added extension methods to IHttpActionResult to easier use in controller:
public static class IHttpActionResultExtensions
{
public static IHttpActionResult WithSuccess(this IHttpActionResult inner, object result = null, string message = null)
{
return new HttpActionResult(inner, true, result, message);
}
public static IHttpActionResult WithError(this IHttpActionResult inner, string message = null)
{
return new HttpActionResult(inner, false,null, message);
}
}
What are the alternatives to deal with wrapping http messages in API Controller?
What weak points do you see in my solution?

BTW, I see some weak points on your approach:
WebAPI is meant to be used to create RESTful Web services. Why are you trying to provide another layer of status and other details? HTTP is rich enough to cover these requirements. For example, you can use standard status codes and a subcode as follows: 500.1, 500.2.
Success or failure is easier to express with HTTP status codes. 2XX range for successful operations, and for an unsuccessful one you can use, for example, 400 (Bad Request). 401 for an unauthorized access... 500 for a server failure...
WebAPI already provides ModelState to let the framework build a response object. Use it and try to don't re-invent the wheel.
Again, keep it simple. Response entity goes on the response body. Success or failure is expressed by status codes. Details about a bad request are added to the ModelState dictionary. An error message should be set to the response's ReasonPhrase.
IHttpActionResult implementations are meant to transform your domain result into an HTTP response. That is, you're in the right track excepting when you try to return your response object as is. My advise is you should use your IHttpActionResult to set every detail on your own response object to standard HTTP semantics, and notify errors using ModelState out-of-the-box approach which works well.

Avoid IHttpActionResult and use HttpResponseException with Business Entity as result type. As in your solution, you cannot write statically typed test cases.
For example,
protected void ThrowHttpError(HttpStatusCode statusCode, string message)
{
throw new HttpResponseException(
new HttpResponseMessage(statusCode) {
ReasonPhrase = message,
// HTTP 2.0 ignores ReasonPhrase
// so we send ReasonPhrase again in the Content
Content = new StringContent(message)
});
}
// some generic option...
protected void ThrowHttpError<T>(HttpStatusCode statusCode, T content)
where T:class
{
throw new HttpResponseException(
new HttpResponseMessage(statusCode) {
ReasonPhrase = "Error",
Content = JsonConvert.Serialize(content)
});
}
Your methods,
public async Task<Product> Get(long id){
var product = await context.Products
.FirstOrDefaultAsync( x=> x.ProductID == id);
if(product==null){
ThrowHttpError(HttpStatusCode.NotFound,
$"Product not found for {id}");
}
if(product.RequiresValidation){
// generic version....
ThrowHttpError(HttpStatusCode.Conflict,
new Product{
ProductID = product.ProductID,
ValidationRequestCode = product.ValidationRequestCode
});
}
return product;
}
For further more, you can customise method ThrowHttpError to suite your needs. Best part is, it is still testable.

Related

Blazor .NET Core 6 OData 8: Post handler in controller receives null object

I have a ASP.NET Core 6 WAsm Client-Server app using OData 8. The client is posting new data to the controller. The proper controller method is indeed being called, the json payload sent by the http client seems ok, yet the controller's post method's data object parameter is null. Why is that and what do I need to fix to make this work?
Here are the relevant code pieces:
Database
public class DS2DbContext : DbContext
{
public DbSet<WatermarkProperties> Watermarks { get; set; }
public DS2DbContext(DbContextOptions<DS2DbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
EDM Model
static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<WatermarkProperties>("Watermarks");
return builder.GetEdmModel();
}
HTTP call to server side controller
async Task<bool> UpdateWatermark()
{
Cloning.CopyProperties (editBuffer, Watermark);
HttpResponseMessage response;
string jsonData = JsonSerializer.Serialize(Watermark);
var stringData = new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = await _httpClient.PostAsync($"DocServer2/Watermarks", stringData);
return (response.StatusCode == System.Net.HttpStatusCode.OK);
}
HTTP payload
Relevant parts of controller
public class WatermarksController : ODataController
{
private readonly DS2DbContext _db;
private readonly ILogger<WatermarksController> _logger;
public WatermarksController(DS2DbContext dbContext, ILogger<WatermarksController> logger)
{
_logger = logger;
_db = dbContext;
}
[EnableQuery]
public async Task<IActionResult> Post([FromBody] WatermarkProperties watermark)
{
_db.Watermarks.Add(watermark);
await _db.SaveChangesAsync();
return Created(watermark.ID.ToString(), watermark);
}
}
"Watermark" is always null in WatermarksController.Post() ... ?

How to get different response type for the same request model using MediatR?

I am trying to understand how MediatR works. Never used this library before. Below code is not actual production code. It is only for understanding purpose.
Lets say I have two RequestHandlers. Each handler takes ProductModel as request but returns different type of response.
public class GetOrdersHandler : IRequestHandler<ProductModel, IEnumerable<Order>>
{
private readonly FakeDataStore _fakeDataStore;
public GetOrdersHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<IEnumerable<Order>> Handle(ProductModel request,CancellationToken cancellationToken
{
return await _fakeDataStore.GetAllOrdersForProduct(request);
}
}
public class SaveProductHandler : IRequestHandler<ProductModel, Product>
{
private readonly FakeDataStore _fakeDataStore;
public SaveProductHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<Product> Handle(ProductModel request,CancellationToken cancellationToken)
{
return await _fakeDataStore.SaveProduct(request);
}
}
Then in the same controller I have two action methods
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<ActionResult> GetAllOrders(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
[HttpPost]
public async Task<ActionResult> SaveProduct(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
}
Based on the MediatR code this may not work. Looks like the Send method creates instance of handler based on Request type. It keeps dictionary of RequestType and corresponding handler.
If my assumption is correct then does that mean I will have to create unique request model for each action method that will be using Send method?

.Net Core API Reject Incomplete JSON Request

I need to reject the Request if the body has incomplete JSON.
I have a .NetCore API, which has a lot of properties. That API does a lot of operations and will be getting a lot of requests, so want to reject if the JSON is incomplete beforehand.
If, I have below AssignmentDetail class,
public class AssignmentDetail
{
public string Name { get; set; }
public string Address { get; set; }
}
Complete JSON Example
{
"Name":"dfsdfsdf",
"Address":"essdfsdfsd",
}
Incomplete JSON Example
{
"Name":"dfsdfsdf"
}
I have some approaches but need something which can be done through in startup, but just for that action.
Using custom Serializable for that AssignmentDetail model (Don't want this approach)
Creating a function to validate the Incomplete JSON like validateIncompleteJSON() (Don't want this approach)
Something in ConfigureServices(IServiceCollection services) but just for that controller action
[HttpPost]
[Route("ProcessAssignment")]
public async Task<AssignmentResponseModel> ProcessAssignment(AssignmentDetail model)
{
var response = new AssignmentResponseModel();
try
{
//can call function here to check the incomlpete JSON
//validateIncompleteJSON();
//var result = await _mediator.Send(queryDetails);
//response = result.Response;
}
catch (Exception ex)
{
throw exception;
}
return response;
}
I don't want to use the Serializable way, as the class is too big and will have to handle all the properties.
Data Annotations add Required Attribute to property
Codes of Model
public class AssignmentDetail
{
[Required]
public string Name { get; set; }
[Required]
public string Address { get; set; }
}
Codes of Controller
[HttpPost]
[Route("/ProcessAssignment")]
public IActionResult ProcessAssignment(AssignmentDetail model)
{
//var response = new AssignmentResponseModel();
//try
//{
// //can call function here to check the incomlpete JSON
// //validateIncompleteJSON();
// //var result = await _mediator.Send(queryDetails);
// //response = result.Response;
//}
//catch (Exception ex)
//{
// throw exception;
//}
//return response;
if (ModelState.IsValid)
{
return Ok("Success");
}
return BadRequest();
}

Override ExecuteResult method and set Content-Type

I've inherited a class from ActionResult and then overrided the ExecuteResult method.
public class CustomResult : ActionResult
{
public object Result { get; set; }
public int StatusCode { get; private set; }
public CustomResult(int statusCode, object Result)
{
this.Result = Result;
this.StatusCode = statusCode;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.HttpContext.Response.StatusCode = StatusCode;
if (StatusDescription != null)
context.HttpContext.Response.StatusDescription = StatusDescription;
HttpResponseBase Response = context.HttpContext.Response;
Response.Charset = "UTF-8";
Response.ContentType = "application/json";
context.HttpContext.Response.Write(JsonConvert.SerializeObject(this));
}
}
When I return a CustomResult object from the controller, the returned Content-Type to the view is always "text/html" and not "application/json" as setted on CustomResult class.
If I run this application locally all works fine, but when I deploy this one to the Azure App Service the Content-Type is always text/html.
Base on my experience, it could work correctly in the Azure Web App. It works correctly for me even I published the WebApp to Azure environment. If it is possible, please have a try to check it with Fiddler tool.
I've found the solution! I've added this line Response.TrySkipIisCustomErrors = true; to the Response object.

controller post actionresult not saving changes to database

I have a post method in my controller that is not saving changes to my database (SQL express). I am using viewmodels and valueinjector to populate the VM from my model. I have checked and the values in the viewmodel and they have changed, but when I call my service:
fixedAssetService.SaveFixedAsset()
and bookmark the following in the service interface:
unitOfWork.Commit()
and pull up the quick watch window for unitOfWork, it has the old value.
All my tables have primary keys and I am using code first. The connection string is valid becasue I can get the items, I just can't save them.
My post method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FixedAssetViewModel evm)
{
var fixedAsset = fixedAssetService.GetFixedAsset(evm.FixedAssetId);
// Use Injector to handle mapping between viewmodel and model
fixedAsset.InjectFrom(evm);
try
{
if (ModelState.IsValid)
{
fixedAssetService.SaveFixedAsset();
return RedirectToAction("Details", "FixedAsset", new { id = fixedAsset.FixedAssetId });
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
My Service:
namespace FixedAssets.Services
{
public interface IFixedAssetService
{
IEnumerable<FixedAsset> GetAll();
IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate);
FixedAsset GetFixedAsset(string id);
void CreateFixedAsset(FixedAsset fixedAsset);
void DeleteFixedAsset(string id);
void SaveFixedAsset();
bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate);
}
public class FixedAssetService : IFixedAssetService
{
private readonly IFixedAssetRepository fixedAssetRepository;
private readonly IUnitOfWork unitOfWork;
public FixedAssetService(IFixedAssetRepository fixedAssetRepository, IUnitOfWork unitOfWork)
{
this.fixedAssetRepository = fixedAssetRepository;
this.unitOfWork = unitOfWork;
}
#region IFixedAssetService Members
public IEnumerable<FixedAsset> GetAll()
{
var fixedAssets = fixedAssetRepository.GetAll();
return fixedAssets;
}
public IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate)
{
IEnumerable<FixedAsset> query = fixedAssetRepository.FindBy(predicate);
return query;
}
public bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate)
{
IQueryable<FixedAsset> query = fixedAssetRepository.FindBy(predicate).AsQueryable();
int count = query.Count();
return count > 0 ? true : false;
}
public FixedAsset GetFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
return fixedAsset;
}
public void CreateFixedAsset(FixedAsset fixedAsset)
{
fixedAssetRepository.Add(fixedAsset);
SaveFixedAsset();
}
public void DeleteFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
fixedAssetRepository.Delete(fixedAsset);
SaveFixedAsset();
}
public void SaveFixedAsset()
{
unitOfWork.Commit();
}
#endregion
}
}
Edit: One thing I forgot to mention is this app was modeled almost exactly after an existing app that worked fine. Not sure if I have references messed up or what, but the other app uses the same methods only different entities
I found my problem. In the app I used as a model for this one I was using a separate unity class. My database factory registration was like this:
.RegisterType<IDatabaseFactory, DatabaseFactory>(new HttpContextLifetimeManager<IDatabaseFactory>())
Now I am using Microsoft.Practices.Unity and Unity.Mvc4 so I changed the registration to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>();
per the comments in the bootstrapper class. When I changed it to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>(new HierarchicalLifetimeManager());
per the suggestions on this post:
Stackoverflow thread
it finally worked!

Resources