I am using NSwag to generate swagger api docs in an ASP.Net Core 2.1 project, which has mixed Web-API controller, MVC controller and Razor Pages. NSwag complains a lot about like the following, while they are valid in ASP.NET. Question: how to filer in Swagger/NSwag to include only a specific Namespace(MyProject.Api) or path (/api/)?
The method 'Post' on path '/api/XXX/Create' is registered multiple times
public ActionResult Create()
{
var doctor = new Doctor();
doctor.create_dt = DateTime.Now;
return View(doctor);
}
//
[HttpPost]
public ActionResult Create(Doctor doctor)
{
if (ModelState.IsValid)
{
theDB.Doctor.Add(doctor);
theDB.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
if you want to exclude just one action method, put the attribute [ApiExplorerSettings(IgnoreApi = true)] on it.
You can also put this on a whole controller class.
For bulk operations across the whole project, you can use a IOperationProcessor, something like this
public class IncludeControllersInSwagger : IOperationProcessor
{
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
bool controllerIsIncluded = TakeADecisionBasedOn(context.ControllerType);
return Task.FromResult(controllerIsIncluded);
}
}
And then wire it in at startup with
RouteTable.Routes.MapOwinPath("swagger", app =>
{
app.UseSwagger(typeof(WebApiApplication).Assembly, settings =>
{
// the usual config, then:
settings.GeneratorSettings.OperationProcessors.Insert(0,
new IncludeControllersInSwagger());
});
});
The you can write code in TakeADecisionBasedOn to include only certain controllers, or exclude a namespace, etc.
Related
I want to run some custom logic for all APIs (asp.net core) that we have in our service before model validation but after model binding. Is this possible? I tried an ActionFilter but it gets called after validation. Resource filter also does not work for us. Appreciate your help.
Web API controllers don't have to check ModelState.IsValid if they have the [ApiController] attribute. In that case, an automatic HTTP 400 response containing issue details is returned when model state is invalid.
One way to achieve what you want is to suppress this behavior.
Add the following code to ConfigureServices:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Then you can add your code to the filter - eg:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
if(context.ActionArguments != null && context.ActionArguments.Count > 0)
{
//WARNING - you should add "safe" code to access the dictionary
//I have hardcoded the parameter name (data) here for sample only.
var model = context.ActionArguments["data"];
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
of course you need to apply the filter as well - in the example case below, I have applied it globally. You can be more specific if you want.
services.AddMvc(
options => options.Filters.Add(new SampleActionFilter())
).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
In your controller code, you can also further use the TryValidateModel method if you want, like so:
[Route("api/[controller]")]
[ApiController]
public class ProcessController : ControllerBase
{
[HttpPost]
public IActionResult Contact(FormDataModel data)
{
bool validated = TryValidateModel(data);
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Id cannot be empty..");
return Ok();
}
return Ok();
}
}
Hope this helps to solve your problem.
I'm using the Fluent Validation framework in my ASP.net MVC 3 project. So far all of my validations have been very simple (make sure string is not empty, only a certain length, etc.) but now I need to verify that something exists in the database or not.
Should Fluent Validation be used in this case?
If the database validation should be done using Fluent Validation, then how do I handle dependencies? The validator classes are created automatically, and I would need to somehow pass it one of my repository instances in order to query my database.
An example of what I'm trying to validate might:
I have a dropdown list on my page with a list of selected items. I want to validate that the item they selected actually exists in the database before trying to save a new record.
Edit
Here is a code example of a regular validation in Fluent Validation framework:
[Validator(typeof(CreateProductViewModelValidator))]
public class CreateProductViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateProductViewModelValidator : AbstractValidator<CreateProductViewModel>
{
public CreateProductViewModelValidator()
{
RuleFor(m => m.Name).NotEmpty();
}
}
Controller:
public ActionResult Create(CreateProductViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var product = new Product { Name = model.Name, Price = model.Price };
repository.AddProduct(product);
return RedirectToAction("Index");
}
As you can see, I never create the Validator myself. This works because of the following line in Global.asax:
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure();
The problem is that now I have a validator that needs to interact with my database using a repository, but since I'm not creating the validators I don't know how I would get that dependency passed in, other than hardcoding the concrete type.
Can't you just create your own validation method where in you would kick-off the database validation?
RuleFor(m => m.name)
.Must(BeInDatabase)
private static bool BeInDatabase(string name)
{
// Do database validation and return false if not valid
return false;
}
I'm using FluentValidation for DataBase validations. just pass the Validation class the session in the Ctor. and do the validation inside the action something like:
var validationResult = new ProdcutValidator(session).Validate(product);
Update: Based on your example I add my example...
public class CreateProductViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateProductViewModelValidator : abstractValidator<CreateProductViewModel>
{
private readonly ISession _session;
public CreateProductViewModelValidator(ISession session)
{
_session = session;
RuleFor(m => m.Name).NotEmpty();
RuleFor(m => m.Code).Must(m, Code => _session<Product>.Get(Code) == null);
}
}
Controller:
public ActionResult Create(CreateProductViewModel model)
{
var validator = new CreateProductViewModelValidator();
var validationResult =validator.Validate(model);
if(!validationResult.IsValid)
{
// You will have to add the errors by hand to the ModelState's errors so the
// user will be able to know why the post didn't succeeded(It's better writing
// a global function(in your "base controller" That Derived From Controller)
// that migrate the validation result to the
// ModelState so you could use the ModelState Only.
return View(model);
}
var product = new Product { Name = model.Name, Price = model.Price };
repository.AddProduct(product);
return RedirectToAction("Index");
}
Second update:
If you insist using parameterless constructor you will have to use some Inversion Of control container, a static class that is something like the Factory of your objects.
use it like this:
public class CreateProductViewModelValidator : abstractValidator<CreateProductViewModel>
{
private readonly ISession _session;
public CreateProductViewModelValidator()
{
_session = IoC.Container.Reslove<ISession>();
RuleFor(m => m.Name).NotEmpty();
RuleFor(m => m.Code).Must(m, Code => _session<Product>.Get(Code) == null);
}
}
You can find many IoC containers, most famous are Windsor and Ninject,
You will need to register- instruct the container once to resolve all the ISession to return your's session object.
The other way this could work for you is using Constructor injection. While this method isn't as clear cut as using an IoC library, it may help if you have a static way of accessing or fetching your session.
public class CreateProductViewModelValidator
{
private ISession _session;
public CreateProductViewModelValidator()
:this(SessionFactory.GetCurrentSession()) //Or some other way of fetching the repository.
{
}
internal CreateProductViewModelValidator(ISession session)
{
this._session = session;
RuleFor(m => m.Name);//More validation here using ISession...
}
}
I have been spending quite a bit of time thinking about this exact same issue. I am using ninject to inject my repository into my web UI layer so that my web UI only accesses the database through an interface.
I am wanting to be able to validate things that access the database such as checking for duplicate names and hence my validation needs to access the injected repository. I think that the best way to do this is to just setup Fluent Validation via the manual method rather than the MVC integrated way. For Example:
Create your validation Class (can pass in repository Interface):
public class CategoryDataBaseValidation : AbstractValidator<CategoryViewModel>
{
private IRepository repository;
public CategoryDataBaseValidation (IRepository repoParam)
{
repository = repoParam;
RuleFor(Category => Category.Name).Must(NotHaveDuplicateName).WithMessage("Name already exists");
}
private bool NotHaveDuplicateName(string name)
{
List<Category> c = repository.Categories.ToList(); //Just showing that you can access DB here and do what you like.
return false;
}
}
}
Then in your controller you can just create an instance of above class and pass in the repository (that ninject would have injected in the controller constructor)
[HttpPost]
public ActionResult Create(CategoryViewModel _CategoryViewModel )
{
CategoryDataBaseValidation validator = new CategoryDataBaseValidation (repository);
ValidationResult results = validator.Validate(_CategoryViewModel );
if (results.IsValid == false)
{
foreach (var failure in results.Errors)
{
//output error
}
}
return View(category);
}
Both the above files can live in the Web UI project and you can then also just use the standard MVC DataAnnotations for client side validation.
Just thought that I would put this up for comment / help someone.
I edited my whole question, so do not wonder :)
Well, I want to have an ActionResult that takes domain model data and some additional parameters, i.e page index and page size for paging a list. It decide itself if it returns a PartialViewResult or a ViewResult depending on the kind of web request (ajax request or not).
The reffered data shall be mapped automatically by using an IMappingService, which is responsible for transforming any domain model data into a view model.
The MappingService uses AutoMapper for simplicity.
MappingActionResult:
public abstract class MappingActionResult : ActionResult
{
public static IMappingService MappingService;
}
BaseHybridViewResult:
public abstract class BaseHybridViewResult : MappingActionResult
{
public const string defaultViewName = "Grid";
public string ViewNameForAjaxRequest { get; set; }
public object ViewModel { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
var usePartial = ShouldUsePartial(context);
ActionResult res = GetInnerViewResult(usePartial);
res.ExecuteResult(context);
}
private ActionResult GetInnerViewResult(bool usePartial)
{
ViewDataDictionary viewDataDictionary = new ViewDataDictionary(ViewModel);
if (String.IsNullOrEmpty(ViewNameForAjaxRequest))
{
ViewNameForAjaxRequest = defaultViewName;
}
if (usePartial)
{
return new PartialViewResult { ViewData = viewDataDictionary, ViewName = ViewNameForAjaxRequest };
}
return new ViewResult { ViewData = viewDataDictionary };
}
private static bool ShouldUsePartial(ControllerContext context)
{
return context.HttpContext.Request.IsAjaxRequest();
}
}
AutoMappedHybridViewResult:
public class AutoMappedHybridViewResult<TSourceElement, TDestinationElement> : BaseHybridViewResult
{
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList)
{
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(TSourceElement model)
{
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
public AutoMappedHybridViewResult(TSourceElement model, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
}
Usage in controller:
public ActionResult Index(int page = 1)
{
return new AutoMappedHybridViewResult<TeamEmployee, TeamEmployeeForm>(_teamEmployeeRepository.GetPagedEmployees(page, PageSize));
}
So as you can see the IMappingService is hidden. The controller should not know anything about the IMappingService interface, when AutoMappedHybridViewResult is used.
Is the MappingActionResult with the static IMappingServer appropriate or am I violating the DI principle?
I think a better design is to have a ViewResultFactory that depends on IMappingService, then you can inject that into your controller. Then you call it like so:
public class MyController : Controller
{
IViewResultFactory _viewResultFactory;
ITeamEmployeeRepository _teamEmployeeRepository;
public MyController(IViewResultFactory viewResultFactory)
{
_viewResultFactory = viewResultFactory;
}
public ActionResult MyAction(int page, int pageSize)
{
return
_viewResultFactory.GetResult<TeamEmployee, TeamEmployeeForm>(
_teamEmployeeRepository.GetPagedEmployees(page, pageSize));
}
}
The implementation would like this (you would need to create overloads for each of your HybridViewResult constructors):
public HybridViewResult<TSourceElement, TDestinationElement> GetResult<TSourceElement, TDestinationElement>(PagedList<TSourceElement> pagedList)
{
return new HybridViewResult<TSourceElement, TDestinationElement>(_mappingService, pagedList);
}
That way you hide the implementation from your controllers, and you don't have to depend on the container.
There are a few different points that you could inject IMappingService. http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx is a good site for help in picking the appropriate extensibility points for .NET MVC.
If you want to stick with having this functionality be a derived ActionResult, then I think you could put the dependency in the ActionInvoker if you want to, but the Controller makes more sense to me. If you don't want the IMappingService in the Controller, you could always wrap it in a HybridViewResultFactory, and access that object in the Controller. In that case your shortcut methods would look like:
public HybridViewResult<TSourceElement, TDestinationElement> AutoMappedHybridView<TSourceElement,TDestinationElement>(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
HybridViewResultFactory.Create<TSourceElement, TDestinationElement>(pagedList, viewNameForAjaxRequest);
}
etc.
I'm not sure why you need to use an ActionResult, but if there is no reason that makes it explicitly necessary, you could create a HybridViewModel class and a HybridViewModelBinder class that is injected with the mapping service dependency.
I am assuming you want to use constructor injection, but if you have the StructureMap dependency in your UI assembly, you could access a static dependency resolver class (like Clowers said).
This question would be easier to give a definite answer to if I understood why you using an ActionResult.
It seems like you are using the action result to handle two functionalities that do not necessarily go together all the time, and that could be used separately. Also, there is not a clear indication that it needs to be in an ActionResult.
Presumably, you could (a) leverage the Automapper functionality for results other than html (ViewResult) output, and (b) you could leverage the functionality of auto-detecting ajax requests without needing to automap the model.
It seems to me like the automapping of the view model could be used to inject the view model into the controller action directly, thus removing the controller's dependency on the IMappingService. What you would need is a ModelBinder class to be injected with your IMappingService (the implementation of which I assume contains a repository or datastore type dependency).
Here is a good article explaining how to leverage model binders: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx.
Then you can overwrite the DefaultModelBinder in the classes that need to be Automapped as follows:
public ActionResult DoItLikeThis([AutoMap(typeof(MyDomainModelClass))]MyViewModelClass viewModel){
//controller action logic
}
Now, regarding the HybridViewResult, I would suggest that you handle this with an Action Filter instead. So, you could just use ActionResult or ViewResultBase as the Result type of your action method and decorate it with an action filter, i.e.:
[AutoSelectViewResult]
public ViewResultBase AndDoThisLikeSo(){
//controller action logic
}
I think overall this will be a much better solution than coupling these two functionalities to an ActionResult.
I am trying to implement user-friendly URLS, while keeping the existing routes, and was able to do so using the ActionName tag on top of my controller (Can you overload controller methods in ASP.NET MVC?)
I have 2 controllers:
ActionName("UserFriendlyProjectIndex")]
public ActionResult Index(string projectName) { ... }
public ActionResult Index(long id) { ... }
Basically, what I am trying to do is I store the user-friendly URL in the database for each project.
If the user enters the URL /Project/TopSecretProject/, the action UserFriendlyProjectIndex gets called. I do a database lookup and if everything checks out, I want to apply the exact same logic that is used in the Index action.
I am basically trying to avoid writing duplicate code. I know I can separate the common logic into another method, but I wanted to see if there is a built-in way of doing this in ASP.NET MVC.
Any suggestions?
I tried the following and I go the View could not be found error message:
[ActionName("UserFriendlyProjectIndex")]
public ActionResult Index(string projectName)
{
var filteredProjectName = projectName.EscapeString().Trim();
if (string.IsNullOrEmpty(filteredProjectName))
return RedirectToAction("PageNotFound", "Error");
using (var db = new PIMPEntities())
{
var project = db.Project.Where(p => p.UserFriendlyUrl == filteredProjectName).FirstOrDefault();
if (project == null)
return RedirectToAction("PageNotFound", "Error");
return View(Index(project.ProjectId));
}
}
Here's the error message:
The view 'UserFriendlyProjectIndex' or its master could not be found. The following locations were searched:
~/Views/Project/UserFriendlyProjectIndex.aspx
~/Views/Project/UserFriendlyProjectIndex.ascx
~/Views/Shared/UserFriendlyProjectIndex.aspx
~/Views/Shared/UserFriendlyProjectIndex.ascx
Project\UserFriendlyProjectIndex.spark
Shared\UserFriendlyProjectIndex.spark
I am using the SparkViewEngine as the view engine and LINQ-to-Entities, if that helps.
thank you!
Just as an addition this this, it might pay to optimize it to only hit the database once for the project...
ActionName("UserFriendlyProjectIndex")]
public ActionResult Index(string projectName)
{
//...
//var project = ...;
return IndexView(project);
}
public ActionResult Index(long id)
{
//...
//var project = ...;
return IndexView(project);
}
private ViewResult IndexView(Project project)
{
//...
return View("Index", project);
}
Sorry, it looks like I am answering my own question!
I returned the call to Index controller inside my "wrapper" controller and then I specified the view name in the Index controller.
ActionName("UserFriendlyProjectIndex")]
public ActionResult Index(string projectName)
{
//...
//var project = ...;
return Index(project.ProjectId);
}
public ActionResult Index(long id)
{
//...
return View("Index", project);
}
What is the best way to protect certain areas of your web application in asp .net mvc. I know we can put [Authorization] attribute at each action, but this seems very tedious since you have to put it all over the place. I'm using membership provider and trying the way I used to do in postback model by setting this protection based on the folder. I use web.config <location> section to protect some folders. I tried this in mvc, it seems to be working, but most of tutorial uses the [Authorization] way.
Which one is the better method?
I'd highly recommend against putting it in the web.config. Actually, so do Conery, Hanselman, Haack, and Guthrie -- though not highly (p223 of Professional ASP.NET MVC 1.0)
Routes are subject to change, especially in MVC. With the WebForm model, routes are physically represented on the file system so you didn't really have to worry about it. In MVC, routes are "dynamic" for lack of a better term.
You could end up with multiple routes mapping to one controller causing a maintenance pain in the web.config. Worse, you could inadvertently have a route invoke a controller accidentally or forget to update the web.config after adding/modifying routes and leave yourself open.
If, however, you secure your controller instead of the actual route, then you don't need to worry about keeping the web.config in sync with the goings-on of the controllers and changing routes.
Just my 2 cents.
One possible solution is to create a "protected controller" and use it as a base class for all the areas of your application that you want to protect
[Authorize]
public class ProtectedBaseController : Controller {
}
public class AdminController : ProtectedBaseController {
...
}
public class Admin2Controller : ProtectedBaseController {
...
}
put [Authorisation] at the top of the controller class. that will lock down the entire controllers actions.
You can put [Authorize] to every contoller you need to secure.
You can add filter GlobalFilters.Add(new AuthorizeAttribute()); in your Startup.cs (or Global.asax) and put [AllowAnonymus] attribute to any controller or action you allow to non-registered users.
If you chose to put [Authorize] to every secure contoller you need to be sure that any controller added by you or anyone other in team will be secure. For this requirement I use such test:
[Fact]
public void AllAuth()
{
var asm = Assembly.GetAssembly(typeof (HomeController));
foreach (var type in asm.GetTypes())
{
if (typeof(Controller).IsAssignableFrom(type))
{
var attrs = type.GetCustomAttributes(typeof (AuthorizeAttribute));
Assert.True(attrs.Any());
}
}
}
I think this way is better than a creating ProtectedContoller, because it make no guarantee that you system have all controllers secure. Also this way doesn't use inheritance, which make project heavier.
Authorization is one way to secure your application; is to apply the attribute to each controller.
Another way is to use the new AllowAnonymous attribute on the login and register actions.
Making secure decisions based on the current area is a Very Bad Thing and will open your application to vulnerabilities.
Code you can get here
As ASP.NET MVC 4 includes the new AllowAnonymous attribute, so you no more need to write that code.
After setting the AuthorizeAttribute globally in global.asax and then whitelisting will be sufficient.
This methods you want to opt out of authorization is considered a best practice in securing your action methods. Thanks.
[Area("AdminPanel")]
public class TestimonialsController : Controller
{
private AppDbContext _context;
private IWebHostEnvironment _env;
public TestimonialsController(AppDbContext context, IWebHostEnvironment env)
{
_context = context;
_env = env;
}
public IActionResult Index()
{
return View(_context.Testimonials);
}
// GET: AdminPanel/Testimonials/Create
public IActionResult Create()
{
return View();
}
// POST: AdminPanel/Testimonials/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Testimonial testimonial)
{
if (!ModelState.IsValid)
{
return View();
}
if (!testimonial.Photo.CheckFileType("image/"))
{
return View();
}
if (!testimonial.Photo.CheckFileSize(200))
{
return View();
}
testimonial.Image = await testimonial.Photo.SaveFileAsync(_env.WebRootPath, "images");
await _context.Testimonials.AddAsync(testimonial);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// GET: AdminPanel/Testimonials/Edit/5
public async Task<IActionResult> Update(int? id)
{
if (id == null)
{
return BadRequest();
}
var testimonial = await _context.Testimonials.FindAsync(id);
if (testimonial == null)
{
return NotFound();
}
return View(testimonial);
}
// POST: AdminPanel/Testimonials/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(int? id, Testimonial newtestimonial)
{
if (id==null)
{
return BadRequest();
}
var oldtestimonial = _context.Testimonials.Find(id);
if (oldtestimonial == null)
{
return NotFound();
}
if (!ModelState.IsValid)
{
return View();
}
if (!newtestimonial.Photo.CheckFileType("image/"))
{
return View();
}
if (!newtestimonial.Photo.CheckFileSize(200))
{
return View();
}
var path = Helper.GetPath(_env.WebRootPath, "images", oldtestimonial.Image);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
newtestimonial.Image = await newtestimonial.Photo.SaveFileAsync(_env.WebRootPath, "images");
oldtestimonial.Image = newtestimonial.Image;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> Delete(int id)
{
if (id == null)
{
return BadRequest();
}
var testimonial = _context.Testimonials.Find(id);
if (testimonial == null)
{
return NotFound();
}
var path = Helper.GetPath(_env.WebRootPath, "images", testimonial.Image);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
_context.Testimonials.Remove(testimonial);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
}