Swagger/Swashbuckle do not recognize JObject in request model - swagger

My Environment: Asp.NET WebAPI、NET Framework 4.5.2、Swashbuckle.Core 5.6.0
For some reason, My controller must Inherit the previous same Controller, like this
public class User100Controller : ApiController
{
[HttpGet]
[AllowAnonymous]
public virtual string Get()
{
return "1.0.0";
}
}
public class User101Controller : User100Controller
{
[HttpGet]
[AllowAnonymous]
public override string Get()
{
return "1.0.1";
}
}
Run directly, and swagger ui page show correctly
sample picture, pls right click
But if I add a Post Action with params, swagger ui do not recongnize Request Model
UserModel and UserModelSex
/// <summary>
/// UserModel
/// </summary>
public class UserModel
{
/// <summary>
/// Name
/// </summary>
public string name { get; set; } = string.Empty;
/// <summary>
/// Age
/// </summary>
public int age { get; set; } = 0;
}
/// <summary>
/// UserModelSex
/// </summary>
public class UserModelSex : UserModel
{
/// <summary>
/// Sex
/// </summary>
public int sex { get; set; } = -1;
}
User100Controller
[HttpPost]
[SwaggerResponse(200, "success", typeof(UserModel))]
public virtual IHttpActionResult SaveUser([FromBody] UserModel model)
{
if (string.IsNullOrEmpty(model.name) || model.age == 0)
{
return Ok("error");
}
//...
return Ok("success");
}
sample picture, pls right click
This is correct effect, Now if I override this SaveUser Action in User101ontroller, and pass the new request model UserModelSex, I got an error, Because of Override Method must have the same param List with the parent Method, So I change it like this
public class User100Controller : ApiController
{
[HttpPost]
[SwaggerResponse(200, "success", typeof(UserModel))]
public virtual IHttpActionResult SaveUser([FromBody] JObject json)
{
var model = json.ToObject<UserModel>();
if (string.IsNullOrEmpty(model.name) || model.age == 0)
{
return Ok("error");
}
//...
return Ok("success");
}
}
public class User101Controller : User100Controller
{
[HttpPost]
[SwaggerResponse(200, "success", typeof(UserModelSex))]
public override IHttpActionResult SaveUser([FromBody] JObject json)
{
var model = json.ToObject<UserModelSex>();
if (string.IsNullOrEmpty(model.name) || model.age == 0 || model.sex == -1)
{
return Ok("error");
}
//...
return Ok("success");
}
}
swagger ui do not recongnize JObject
sample picture, pls right click
how to show UserModel and UserModeSex in request model, like pic 2

Finally, I solve it by myself
add SwaggerRequestModelAttribute.cs
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SwaggerRequestModelAttribute : Attribute
{
public Type RequestModel { get; private set; }
public string ModelName { get; private set; }
public SwaggerRequestModelAttribute(Type requestModel)
{
RequestModel = requestModel;
ModelName = requestModel.Name;
}
}
then, Mark [SwaggerRequestModel] to the Controllers
[HttpPost]
[SwaggerRequestModel(typeof(UserModel))]
public virtual IHttpActionResult SaveUser([FromBody] JObject json)
{
var model = json.ToObject<UserModel>();
if (string.IsNullOrEmpty(model.name) || model.age == 0)
{
return Ok("error");
}
//...
return Ok("success");
}
Add ModelInBodyOperationFilter.cs
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null) operation.parameters = new List<Parameter>();
var attribute = apiDescription.GetControllerAndActionAttributes<SwaggerRequestModelAttribute>();
if (attribute.Any())
{
if (operation.parameters.Count > 0 && operation.parameters[0].schema.type == "object")
{
if (!schemaRegistry.Definitions.ContainsKey(attribute.First().ModelName))
schemaRegistry.GetOrRegister(attribute.First().RequestModel);
operation.parameters.RemoveAt(0);
operation.parameters.Add(new Parameter
{
name = "-",
#in = "body",
required = true,
schema = new Schema { #ref = $"#/definitions/{attribute.First().RequestModel.Namespace}.{attribute.First().ModelName}" }
});
}
}
}
}
also, in SwaggerConfig.Register()
c.UseFullTypeNameInSchemaIds();
c.OperationFilter<ModelInBodyOperationFilter>();
at last, run the app and preview it
sample picture, pls right click

Related

How to separate business logic layer using ModelState wrapper class in .net core mvc

I am developing Web Applications using ASP.Net MVC 5 (.net framework), now trying develop my next Project using .NET Core 3.1 with Entity framework Core (with Code first approach).
I am trying separate business logic in a separate Wrapper class like:
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
#endregion
}
In the EmployeeRepo class:
private Models.IValidationDictionary _modelState;
public EmployeeRepo(Models.IValidationDictionary modelState)
{
_modelState = modelState;
}
public int Create(Models.Employee ObjToCreate)
{
if (!Validate(ObjToCreate))
return 0;
_context.Employee.Add(ObjToCreate);
_context.SaveChanges();
return ObjToCreate.Id;
}
protected bool Validate(Models.Employee objToValidate)
{
if (objToValidate.Name.Trim().Length == 0)
_modelState.AddError("Name", "Name is required.");
if (null == objToValidate.DOB)
_modelState.AddError("DOB", "DOB is required");
return _modelState.IsValid;
}
In the Controller:
private Repository.IEmployeeRepo repo;
public EmployeesController(ApplicationDbContext context)
{
_context = context;
repo = new Repository.EmployeeRepo(new Models.ModelStateWrapper(this.ModelState));
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("Name,DOB,Department")] Employee employee)
{
var respId = repo.Create(employee);
if (0 != respId.Id)
{
return RedirectToAction(nameof(Details), new { id = respId.Id });
}
return View(employee);
}
I am expecting ModelState errors to be update in the controller which is added by the wrapper class, but model validation error not updating in the Controller.
Thanks for your time and for any response.
With Best Regards,
Raju Mukherjee
You just want to use a custom verification method to verify these two
fields, right?
The method you wrote is too complicated.
To bind the corresponding error message, you need to add your custom verification attribute above the corresponding fields like [Required].
In fact, you only need to inherit the ValidationAttribute method and rewrite the IsValid method.
public class Employee
{
public int Id { get; set; }
[CustomValidate]
public string Name { get; set; }
[CustomValidate]
public string DOB { get; set; }
public string Department{ get; set; }
}
Custom Method:
public class CustomValidateAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || string.IsNullOrEmpty(value.ToString().Trim()))
{
return new ValidationResult(validationContext.DisplayName + " is required.");
}
return ValidationResult.Success;
}
}
Create:
[HttpPost]
public IActionResult Create([Bind("Name,DOB,Department")]Employee employee)
{
if (ModelState.IsValid)
{
_context.Employee.Add(employee);
_context.SaveChanges();
return RedirectToAction(nameof(Details), new { id = employee.Id });
}
return View(employee);
}
Here is the test result:

How to implement some code in many actions of many controllers

I have controllers with actions, which works with some entity (Driver). Also, each Driver linked with identity profile.
public async Task<ActionResult> Details(int? id)
{
if ((id == null))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
DriverDetailVM model = mapper.Map<DriverDetailVM>(db.Drivers.Find(id));
if ((model == null))
{
return HttpNotFound();
}
return View(model);
}
public async Task<ActionResult> Edit(int? id = null, bool isNewUser = false)
{
/////////
}
If user has role "Superadmin" then he has access to pages with any id value. If user has role "Driver" then we should have access only if id value is the same, as his profile. I try to implement it on ActionFilter:
public class DriverAccessActionFilterAttribute : ActionFilterAttribute
{
public string IdParamName { get; set; }
public int DriverID { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.IsInRole("Driver"))
{
if (filterContext.ActionParameters.ContainsKey(IdParamName))
{
var id = filterContext.ActionParameters[IdParamName] as Int32;
if (id != DriverID)
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
else
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
else
base.OnActionExecuting(filterContext);
}
}
but when I try to use this code:
[DriverAccessActionFilter(DriverID = currentUser.DriverId, IdParamName = "id")]
public async Task<ActionResult> Details(int? id)
{
it does not want to be compiled, because
An object reference is required for the non-static field, method, or
property
how to implement it?
Attribute parameters are evaluated at compile-time, not at runtime. So they have to be compile time constants. You can't pass a value to an action attribute at run-time. i.e in [DriverAccessActionFilter(DriverID = currentUser.DriverId, IdParamName = "id")] you are passing DriverID = currentUser.DriverId. An attribute is used as controller/action metadata and metadata needs to be compiled in assembly. This is why attributes can take only constant values.
You have to change your attribute as follows:
Use Dependency Injection to Inject your service which returns logged in user.
Or Implement a custom Principal and assign it current request principal.
You can modify your attribute as follow in case you implement CustomPrinicpal:
public class DriverAccessActionFilterAttribute : ActionFilterAttribute
{
public string IdParamName { get; set; }
private int DriverID { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.IsInRole("Driver"))
{
var customPrincipal = filterContext.HttpContext.User as CustomPrincipal;
DriverID = customPrincipal.Id;
// Rest of you logic
}
else
base.OnActionExecuting(filterContext);
}
}
In case you choose DI path then you can use the following snippet:
public class DriverAccessActionFilterAttribute : ActionFilterAttribute
{
public string IdParamName { get; set; }
private int DriverID { get; set; }
public DriverAccessActionFilterAttribute(IYourIdentityProvider provider)
{
DriverID = provider.LoggedInUserID;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Your logic
}
}
and then use your attribute as [DriverAccessActionFilter(IdParamName = "id")]

How do I return a massive dynamic in mvc 4 to a view?

I am pulling rows from a database looking for specific columns. Right now it's pulling 5 rows and I need to reference the correct columns for each of the rows with the model. Here's my Controller:
public ActionResult Index()
{
//Use mimetype as key to find correct rows
var mimetype = "mcv";
dynamic LookupData = GetVideoUrls(mimetype);
return View(LookupData);
}
private dynamic GetVideoUrls(string mimetype)
{
var VideoService = new AppServices.Video.GetAllVideoByMimeType();
dynamic videoFiles = VideoService.Execute(mimetype);//Execute grabs the rows from the database
return (videoFiles);
}
In my view I have:
#model IEnumerable<dynamic>
VideoFiles returns five rows with 13 columns. I need to access 2 of the columns for each of the five rows in the view. How do I do this?
Update
Here's my model:
public class LoginVideoModel
{
public LoginVideoModel(string englishurl, string spanishurl)
{
EnglishVideoUrl = englishurl;
SpanishVideoUrl = spanishurl;
}
public string EnglishVideoUrl
{
get;
set;
}
public string SpanishVideoUrl
{
get;
set;
}
}
public class WelcomeVideoModel
{
public WelcomeVideoModel(string englishurl, string spanishurl)
{
EnglishVideoUrl = englishurl;
SpanishVideoUrl = spanishurl;
}
public string EnglishVideoUrl
{
get;
set;
}
public string SpanishVideoUrl
{
get;
set;
}
}
public class BenefitVideoModel
{
public BenefitVideoModel(string englishurl, string spanishurl)
{
EnglishVideoUrl = englishurl;
SpanishVideoUrl = spanishurl;
}
public string EnglishVideoUrl
{
get;
set;
}
public string SpanishVideoUrl
{
get;
set;
}
}
public class MyEnrollmentVideoModel
{
public MyEnrollmentVideoModel(string englishurl, string spanishurl)
{
EnglishVideoUrl = englishurl;
SpanishVideoUrl = spanishurl;
}
public string EnglishVideoUrl
{
get;
set;
}
public string SpanishVideoUrl
{
get;
set;
}
}
public class AdminSideVideoModel
{
public AdminSideVideoModel(string englishurl, string spanishurl)
{
EnglishVideoUrl = englishurl;
SpanishVideoUrl = spanishurl;
}
public string EnglishVideoUrl
{
get;
set;
}
public string SpanishVideoUrl
{
get;
set;
}
}
Hope this helps as well.
Update
Here's the videoservice.execute code:
public class GetAllVideoByMimeType
{
public dynamic Execute(string mimetype)
{
return DBRepository.GetAllByCritera<DataContainers.File>("WHERE MimeType = #0", mimetype);
}
}
update 3
Here's the repository:
/// <summary>
/// Obtain one or more items from db based on Where Clase
/// </summary>
/// <typeparam name="T">Table Type</typeparam>
/// <param name="whereClause">Standard WHERE clause with paramterized statements
/// Example: WHERE someID=#0",args: 4
/// </param>
/// <param name="arguments"></param>
/// <returns></returns>
public static IEnumerable<dynamic> GetAllByCritera<T>(string whereClause, params object[] arguments)
{
var model = new DBTableModel<T>();
return model.All(where: whereClause, args: arguments);
}
What if I tried this:
public ActionResult Index()
{
//Use mimetype as key to find correct rows
var mimetype = "mcv";
dynamic LookupData = GetVideoUrls(mimetype);
return View(LookupData);
}
private dynamic GetVideoUrls(string mimetype)
{
var VideoService = new AppServices.Video.GetAllVideoByMimeType();
List<dynamic> videoFiles = VideoService.Execute(mimetype);
ApplicationVideoModel appvids = new ApplicationVideoModel();
dynamic LoginFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "LoginController").FirstOrDefault();
dynamic WelcomeFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "WelcomeController").FirstOrDefault();
dynamic BenefitFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "BenefitController").FirstOrDefault();
dynamic MyEnrollmentFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "MyEnrollmentController").FirstOrDefault();
dynamic AdminFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "AdminSideController").FirstOrDefault();
return LoginFiles;
}
Each of the dynamics returns the row I need, but how would I send each one to the view?
Based on the sample models you provided, your common model object can look like this
public class VideoUrlModel {
public string EnglishVideoUrl { get; set; }
public string SpanishVideoUrl { get; set; }
}
The GetVideoUrls can then be updated as follows
private IEnumrable<VideoUrlModel> GetVideoUrls(string mimetype) {
var VideoService = new AppServices.Video.GetAllVideoByMimeType();
var videoFiles = VideoService.Execute(mimetype);
var models = videoFiles.Select(row =>
new VideoUrlModel {
EnglishVideoUrl = row.englishvideourl,
SpanishVideoUrl = row.spanishvideourl
});
return models;
}
Since VideoService.Execute returns IEnumerable<dynamic> you can iterate over the rows using the LINQ Select extension and create the model items as needed.
So now in your controller can pass the collection to the view as its model
public ActionResult Index() {
//Use mimetype as key to find correct rows
var mimetype = "mcv";
var LookupData = GetVideoUrls(mimetype);
return View(LookupData);
}
your view will expect the model as,
#model IEnumerable<VideoUrlModel>
and have access to the collection passed as the model for the view
Here's what worked for my application:
public ActionResult Index()
{
//Use mimetype as key to find correct rows
var mimetype = "mcv";
var VideoService = new AppServices.Video.GetAllVideoByMimeType();
List<dynamic> videoFiles = VideoService.Execute(mimetype);
dynamic mymodel = new System.Dynamic.ExpandoObject();
mymodel.LoginFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "LoginController").FirstOrDefault();
mymodel.BenefitFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "BenefitController").FirstOrDefault();
mymodel.WelcomeFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "WelcomeController").FirstOrDefault();
mymodel.MyEnrollmentFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "MyEnrollmentController").FirstOrDefault();
mymodel.AdminSideFiles = videoFiles.Where(v => v.OriginalFileNameWithoutExtension == "AdminSideController").FirstOrDefault();
return View(mymodel);
}
As you can see I'm using mymodel. to build the model and I can access this in the view using:
#Model.LoginFiles.EnglishVideoURL
Thanks for the help!

JSONIgnore data annotations on class

I have a public property which is an object that contains numerous properties itself. Using ASP.net MVC, when I serialize the JSON data I simply add the [JsonIgnore] attribute wherever I use the object so it doesn't display the contents.
Is there a way to add the [JsonIgnore] attribute to the class so it never is serialized?
//[JsonIgnore] ??
public class DataObj
{
public string ConnectionName { get; set; }
public string Query { get; set; }
...
}
public class Customer
{
public string First { get; set; }
public string Last { get; set; }
[JsonIgnore]
public DataObj Foo { get; set; }
}
public class ShipAddress
{
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
[JsonIgnore]
public DataObj Foo { get; set; }
}
My solution after receiving the code provided by jvanrhyn.
Also, here is a link that explains more.
public class DataObjFilterContractResolver : DefaultContractResolver
{
public static readonly DataObjFilterContractResolver Instance = new DataObjFilterContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member,MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType.Name.StartsWith("DataObj") || property.PropertyName == "DataObj")
{
property.ShouldSerialize = instance => false;
}
return property;
}
}
public class UtcJsonResult : JsonResult
{
public UtcJsonResult(object data)
{
Data = data;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
}
private const string DateFormat = #"yyyy-MM-dd HH:mm:ssZ";
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
if (Data == null) return;
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
if (ContentEncoding != null) response.ContentEncoding = ContentEncoding;
var isoConvert = new IsoDateTimeConverter {DateTimeFormat = DateFormat};
JsonConvert.DefaultSettings =
() => new JsonSerializerSettings
{ ContractResolver = new DataObjFilterContractResolver()}; //<--- Used here
var json = JsonConvert.SerializeObject(Data, isoConvert);
response.Write(json);
}
}
You can add a Contract Resolver in your project.
public class ShouldSerializeContractResolver : DefaultContractResolver
{
public new static readonly ShouldSerializeContractResolver Instance =
new ShouldSerializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member,
MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType == typeof(DataObj))
{
property.ShouldSerialize =
instance =>
{
return false;
};
}
return property;
}
}

What do I use for a return type when I am returning data from an anonymous class with Web API?

I have the following ASP MVC4 code:
[HttpGet]
public virtual ActionResult GetTestAccounts(int applicationId)
{
var testAccounts =
(
from testAccount in this._testAccountService.GetTestAccounts(3)
select new
{
Id = testAccount.TestAccountId,
Name = testAccount.Name
}
).ToList();
return Json(testAccounts, JsonRequestBehavior.AllowGet);
}
Now I am converting this to work with Web API. For this can someone tell me
what my return type should be if I am returning an anonymous class as here?
It should be an HttpResponseMessage
public class TestAccountsController: ApiController
{
public HttpResponseMessage Get(int applicationId)
{
var testAccounts =
(
from testAccount in this._testAccountService.GetTestAccounts(3)
select new
{
Id = testAccount.TestAccountId,
Name = testAccount.Name
}
).ToList();
return Request.CreateResponse(HttpStatusCode.OK, testAccounts);
}
}
But good practices dictate that you should use view models (as you should have done in your ASP.NET MVC application as well by the way):
public class TestAccountViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
and then:
public class TestAccountsController: ApiController
{
public List<TestAccountViewModel> Get(int applicationId)
{
return
(
from testAccount in this._testAccountService.GetTestAccounts(3)
select new TestAccountViewModel
{
Id = testAccount.TestAccountId,
Name = testAccount.Name
}
).ToList();
}
}

Resources