Kentico 13 .NET Core MVC Routing Patters - kentico

I have the following code in my Startup.cs
endpoints.MapControllerRoute(
name: "Default",
pattern: "{controller}/{action}/{id?}"
);
Routing doesn't work for the following method in the controller and returns 404
public async Task<ActionResult> Index(int id)
{
}
The url I'm trying to navigate is
/home/our-villages/property/100
However, it's working fine without the parameter value '100'. It hits the controller action in this case.
/home/our-villages/property
I believe I'm missing something in reagards to setting up the routing with parameters here. Any idea?

Have you tried making the id parameter for the method optional?
Try doing this
public async Task<ActionResult> Index(int id = 0)
{
}
or
public async Task<ActionResult> Index(int? id)
{
}

Related

Web API 2 Basic Authentication and allow actions not marked [Authorize]

I have been looking at Basic Authentication in Web Api2 and don’t seem to find an explanation for something I am confused about.
I created a web api application project with individual authentication in Visual studio 2017.
I have the default code
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
I call these actions via postman, browser etc all good.
If I add the [Authorize] attribute to one of the methods I get 401 unauthorized response as expected.
So far so good.
I then add basic authentication by creating a class derived from AuthorizationFilterAttribute
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = userName == "ade" && password == "password";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
HttpContext.Current.User = principal;
return;
}
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
I register the filter in WebApiConfig.cs
config.Filters.Add(new BasicAuthenticationAttribute());
I use postman to call the action marked with [Authorize] and send with header Authorization: Basic YWRlOnBhc3N3b3Jk
The request is authorized and I get my action response. All good.
Now I call the action that is not marked with [Authorize] without a Authorization header from postman expecting to get a response but the OnAuthorization is called and obviously returns HandleUnathorized(actionContext); I only expected the OnAuthorization method to be called where an action is marked with [Authorize]
So now I am thinking what is the point of the [Authorize] attribute because OnAuthorization is called regardless so what is the point of marking actions [Authorize] attribute?
Secondly, I added the following method to my class
private static bool SkipAuthorization(HttpActionContext actionContext)
{
Contract.Assert(actionContext != null);
return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
|| actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}
I call this method at the beginning of OnAuthorization
if (SkipAuthorization(actionContext)) return;
If I mark my actions with [AllowAnonymous] it works.
If there is no [Authorize] attribute on the controller or specific actions then surely the OnAuthorization should also be skipped?
I just don't see the point of using [Authorize], I am clearly missing something here, am I doing something wrong or do I need to mark the actions with [AllowAnonymous] to exclude them.
If you are using [Authorize] attribute and windows authentication, then authorization will done automatically, you don't need to do any special configuration, but any special case if you need to override [Authorize] class then your class is like below,
Instead of inheriting AuthorizationFilterAttribute, you can
inherit AuthorizeAttribute
public class BasicAuthenticationAttribute : AuthorizeAttribute
{
//your override methods
}
Instead of using [Authorize] attribute, use your derived class name. In your case use [BasicAuthenticationAttribute], not [Authorize]
Thanks Fran you set me off on the right path.
I commented out the following line
config.Filters.Add(new BasicAuthenticationAttribute());
I used the following attributes in controller
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Authorize]
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
}
If I called the action get() I got a response, OnAuthorisation was not called.
If I call get(int id) I get 401 Unauthorised and OnAuthorisation is not called.
I removed the [Authorize] from get(int id) action
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
and it all worked, OnAuthorisation was called as expected.

SwaggerRequestExample attribute does not work in ASP.NET MVC 5 (.NET Framework 4.5.2)

I am toying with Swashbuckle.Examples package (3.10.0) in an ASP.NET MVC project. However, I cannot make request examples appear within the UI.
Configuration (SwaggerConfig.cs)
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c => {
c.SingleApiVersion("v1", "TestApp.Web");
c.IncludeXmlComments(string.Format(#"{0}\bin\TestApp.Web.xml", System.AppDomain.CurrentDomain.BaseDirectory));
c.OperationFilter<ExamplesOperationFilter>();
c.OperationFilter<DescriptionOperationFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
})
.EnableSwaggerUi(c => { });
}
Request example classes
public class EchoRequestExample : IExamplesProvider
{
public object GetExamples()
{
return new EchoInput { Value = 7 } ;
}
}
public class EchoInput
{
public int Value { get; set; }
}
Action
[HttpGet]
[Route("Echo")]
[CustomApiAuthorize]
[SwaggerRequestExample(typeof(EchoInput), typeof(EchoRequestExample))]
[ResponseType(typeof(EchoServiceModel))]
public HttpResponseMessage Echo([FromUri] EchoInput model)
{
var ret = new EchoServiceModel
{
Username = RequestContext.Principal.Identity.Name,
Value = value
};
return Request.CreateResponse(HttpStatusCode.OK, ret);
}
Swagger UI shows xml comments and output metadata (model and an example containing default values), but shows no request example. I attached to process and EchoRequestExample.GetExamples is not hit.
Question: How to make SwaggerRequestExample attribute work in ASP.NET MVC 5?
Note: Windows identity is used for authorization.
I received an answer from library owner here:
Swagger request examples can only set on [HttpPost] actions
It is not clear if this is a design choice or just a limitation, as I find [HttpGet] examples also relevant.
I know the feeling, lot's of overhead just for an example, I struggle with this for a while, so I created my own fork of swashbuckle, and after unsuccessful attempts to merge my ideas I ended up detaching and renaming my project and pushed to nuget, here it is: Swagger-Net
An example like that will be:
[SwaggerExample("id", "123456")]
public IHttpActionResult GetById(int id)
{
Here the full code for that: Swagger_Test/Controllers/IHttpActionResultController.cs#L26
Wondering how that looks like on the Swagger-UI, here it is:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=IHttpActionResult#/IHttpActionResult/IHttpActionResult_GetById

Calling Hub method from a controller's action in ASPNetCore MVC application using SignalR

I would like the ASPNetCore2.0 webapp I'm working on to send a notification to specific users using SignalR. I would like to call the hub's method from another controller's action (as opposed to a client's JS call).
I have learned that this is not how SignalR is intended to be used, but I've found many users who had the same 'desire' and also some solutions.
I have checked several proposed solutions, but the simplest and cleaner seemed to be the accepted answer to this post: Get Hub Context in SignalR Core from within another object.
So I gave it a go, and I get no errors at all. The server's output is error-free, and so are the browser's console and network tabs (I'm using Chrome). When debugging, the flow is smooth and the program does exactly what it should do... except the users don't get any notification...
Do any of you spot the problem?
I created a class that contains the shared methods for the hub:
using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace WebApp.Hubs
{
public class HubMethods
{
private readonly IHubContext<PostsHub> _hubContext;
public HubMethods(IHubContext<PostsHub> hubContext)
{
_hubContext = hubContext;
}
public async Task Notify(string postId, string sender, List<string> users)
{
await _hubContext.Clients.Users(users).SendAsync("Notify", sender, postId);
}
}
}
Then I created a hub:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace WebApp.Hubs
{
[Authorize]
public class PostsHub : Hub
{
private HubMethods _hubMethods;
public PostsHub(HubMethods hubMethods)
{
_hubMethods = hubMethods;
}
public async Task Notify(string postId, string sender, List<string> users)
{
await _hubMethods.Notify(postId, sender, users);
}
}
}
Added these bits to Startup's ConfigureServices method:
[...]// Services before these...
services.AddSignalR();
services.AddScoped<HubMethods>();
services.AddMvc();
And Startup's Configure method:
app.UseSignalR(routes =>
{
routes.MapHub<PostsHub>("/postshub");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Then these lines to the view:
<script src="~/lib/signalr/signalr.js"></script>
#await Html.PartialAsync("_NotifyScriptsPartial")
And this is "_NotifyScriptsPartial.cshtml":
<script>
var connection = new signalR.HubConnectionBuilder().withUrl('/PostsHub').build();
connection.on('Notify', function (sender, postId) {
var post = postId;
var sentBy = sender;
var content = '<a href=\'#\' class=\'close\' data-dismiss=\'alert\' aria-label=\'close\'>×</a>' +
'You just received a new comment from <strong>' +
sentBy + '</strong>. Click <a href = \'#\' class=\'alert-link\' >here</a> to view the post.'
var alert = document.createElement('div');
alert.classList.add('alert', 'alert-info', 'alert-dismissible');
alert.html(content);
document.getElementById('pageContent').appendChild(alert);
});
</script>
Finally, in the controller that is supposed to send the notification, I added these:
public class PostsController : Controller
{
private readonly HubMethods _hubMethods;
public PostsController(HubMethods hubMethods)
{
_hubMethods = hubMethods;
}
// POST: Create a new post
[Authorize]
[HttpPost]
public async Task<IActionResult> Create(DetailsModel model, List<string> readers)
{
if (ModelState.IsValid)
{
// Do stuff here... including creating the newPostId, userId and receipients variables used below
await _hubMethods.Notify(newPostId, userId, receipients);
// Do more stuff and eventually...
return View();
}
}
}
Any idea?
In Asp.Net Core 2.1 I can use hub like this, It solves my problem, also You used like this in your controller. Hope it helps.
public class SomeController : Controller
{
private readonly IHubContext<MyHub> _myHub;
public SomeController (IHubContext<MyHub> myHub)
{
_myHub = myHub;
}
public void SomeAction()
{
//for your example
_myHub.Clients.All.SendAsync("Notify", "data");
}
}
I can get the "data" text from browser's console. If you use jQuery in your project, add those codes between jQuery(document).ready(function () { }); because you tried to load a partial html and I think your code needs to run after ready() event. Sorry If I misunderstood you.

Is it good way for protecting the form model in Asp.Net Core MVC using DataProtection

I have tried create the Asp.Net Core MVC app where I want to protect the form model especially Id.
I have found the posibility with DataProtection with method - Protect and Unprotect string.
I've used this implementation:
public class HomeController : Controller
{
readonly IDataProtector _protector;
private readonly IUserRepository _userRepository;
public HomeController(IDataProtectionProvider provider, IUserRepository userRepository)
{
_protector = provider.CreateProtector("DataProtectionDemo.Controllers.HomeController");
_userRepository = userRepository;
}
[HttpGet]
public async Task<IActionResult> Index(int id)
{
var user = await _userRepository.GetUserDetail(id);
user.Id = _protector.Protect(user.Id);
return View(user);
}
[HttpPost]
public async Task<IActionResult> Index(UserViewModel model)
{
try
{
model.Id = _protector.Unprotect(model.Id);
await _userRepository.SaveUser(model);
return RedirectToAction(nameof(Index));
}
catch (Exception e)
{
model.Error = e.Message;
return View(model);
}
}
In this case I want to protect UserId in hidden field with encrypted string, but I don't know if this using of Dataprotection is correct way. I know of posibilities around Authorization Policy and it might be next step check user permission but I am wondering about this additional way as create better protection.
Is it good way how protect the form model?
I have opened this issue on Github in DataProtection topic:
https://github.com/aspnet/DataProtection/issues/278
The answer was that this solution should work fine.

Specific TableController name not working

I have an extremely odd error and wondered if anyone knew the reason for this.
When I create a new DataObject and TableController called Content and ContentController respectively, it doesn't register the tablecontroller and the help documentation it automatically generates has lost its styling.
I can't connect to the controller at all but all other controllers work as expected.
If I just rename it to DataController and that's just the name of the controller, not the dataobject everything works perfectly.
Is ContentController a reserved word of some kind or is this just specifically happening on my machine?
public class DataController : TableController<Content>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileContext context = new MobileContext();
DomainManager = new EntityDomainManager<Content>(context, Request, Services);
}
// GET tables/Content
public IQueryable<Content> GetAllContent()
{
return Query();
}
// GET tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Content> GetContent(string id)
{
return Lookup(id);
}
// PATCH tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Content> PatchContent(string id, Delta<Content> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<IHttpActionResult> PostContent(Content item)
{
Content current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Content/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteContent(string id)
{
return DeleteAsync(id);
}
}
An MVC project will create an application directory called Content. This will override your route mapping to the ContentController.
You can get around this if desired through changing RouteMaps and other trickery although probably the simpliest answer is to change the name of the controller...

Resources