programing

()의 C# 이름을 ASP와 함께 사용하는 방법.NET MVC URL.액션.

muds 2023. 8. 24. 22:31
반응형

()의 C# 이름을 ASP와 함께 사용하는 방법.NET MVC URL.액션.

새 제품을 사용할 수 있는 권장 방법이 있습니까?

nameof()

ASP의 식입니다.컨트롤러 이름에 대한 NET MVC?

Url.Action("ActionName", "Home")  <------ works

Url.Action(nameof(ActionName), nameof(HomeController)) <----- doesn't work

분명히 (HomeController)이름이 "HomeController"로 변환되기 때문에 작동하지 않으며 MVC에 필요한 것은 단지 "Home"입니다.

저는 제임스가 제안한 확장 방법이 마음에 듭니다.한 가지 문제가 있습니다. 비록 당신이 사용하고 있지만nameof()그리고 마법의 끈을 제거했지만, 여전히 안전 유형의 작은 문제가 있습니다. 당신은 여전히 끈으로 작업하고 있습니다.따라서 확장 메서드를 사용하거나 유효하지 않은 임의 문자열(예: 컨트롤러 이름을 잘못 입력)을 제공하는 것을 잊기가 매우 쉽습니다.

일반 매개 변수가 대상 컨트롤러인 컨트롤러에 대한 일반 확장 방법을 사용하여 James의 제안을 개선할 수 있다고 생각합니다.

public static class ControllerExtensions
{
    public static string Action<T>(this Controller controller, string actionName)
        where T : Controller
    {
        var name = typeof(T).Name;
        string controllerName = name.EndsWith("Controller")
            ? name.Substring(0, name.Length - 10) : name;
        return controller.Url.Action(actionName, controllerName);
    }
}

이제 사용법이 훨씬 깨끗해졌습니다.

this.Action<HomeController>(nameof(ActionName));

확장 방법을 고려합니다.

public static string UrlName(this Type controller)
{
  var name = controller.Name;
  return name.EndsWith("Controller") ? name.Substring(0, name.Length - 10) : name;
}

그런 다음 다음을 사용할 수 있습니다.

Url.Action(nameof(ActionName), typeof(HomeController).UrlName())

하게 해야 합니다.routeValues적절하게 처리되며 항상 다음과 같이 처리되지는 않습니다.querystring가치. .그러나 작업이 컨트롤러와 일치하는지 확인해야 합니다.

내 솔루션은 다음에 대한 확장 오버로드를 생성하는 것입니다.Url.Action.

<a href="@(Url.Action<MyController>(x=>x.MyAction))">Button Text</a>

여러 유형에 대해 단일 매개 변수 작업에 대한 오버로드가 있습니다.,▁i를 통과해야 한다면,routeValues...

<a href="@(Url.Action<MyController>(x=>x.MyAction, new { myRouteValue = myValue }))">Button Text</a>

오버로드를 명시적으로 생성하지 않은 복잡한 매개 변수가 있는 작업의 경우 작업 정의와 일치하도록 컨트롤러 유형을 지정해야 합니다.

<a href="@(Url.Action<MyController,int,string>(x=>x.MyAction, new { myRouteValue1 = MyInt, MyRouteValue2 = MyString}))">Button Text</a>

동작이 물론대부분경동작일컨한내트여유히전로에므지되러롤업의이우▁use▁of여▁still히전▁just▁stays물로▁most므▁action유ller론▁the를 사용합니다.nameof그 사람들을 위하여.

<a href="@Url.Action(nameof(MyController.MyAction))">Button Text</a>

때부터routeValues작업 매개 변수와 반드시 일치할 필요는 없습니다. 이 솔루션을 사용하면 유연성을 확보할 수 있습니다.

확장 코드

namespace System.Web.Mvc {
    public static class UrlExtensions {

    // Usage : <a href="@(Url.Action<MyController>(x=>x.MyActionNoVars, new {myroutevalue = 1}))"></a>
    public static string Action<T>(this UrlHelper helper,Expression<Func<T,Func<ActionResult>>> expression,object routeValues = null) where T : Controller
        => helper.Action<T>((LambdaExpression)expression,routeValues);

    // Usage : <a href="@(Url.Action<MyController,vartype1>(x=>x.MyActionWithOneVar, new {myroutevalue = 1}))"></a>
    public static string Action<T, P1>(this UrlHelper helper,Expression<Func<T,Func<P1,ActionResult>>> expression,object routeValues = null) where T : Controller
        => helper.Action<T>(expression,routeValues);

    // Usage : <a href="@(Url.Action<MyController,vartype1,vartype2>(x=>x.MyActionWithTwoVars, new {myroutevalue = 1}))"></a>
    public static string Action<T, P1, P2>(this UrlHelper helper,Expression<Func<T,Func<P1,P2,ActionResult>>> expression,object routeValues = null) where T : Controller
        => helper.Action<T>(expression,routeValues);

    // Usage : <a href="@(Url.Action<MyController>(x=>x.MyActionWithOneInt, new {myroutevalue = 1}))"></a>
    public static string Action<T>(this UrlHelper helper,Expression<Func<T,Func<int,ActionResult>>> expression,object routeValues = null) where T : Controller
        => helper.Action<T>((LambdaExpression)expression,routeValues);

    // Usage : <a href="@(Url.Action<MyController>(x=>x.MyActionWithOneString, new {myroutevalue = 1}))"></a>
    public static string Action<T>(this UrlHelper helper,Expression<Func<T,Func<string,ActionResult>>> expression,object routeValues = null) where T : Controller
        => helper.Action<T>((LambdaExpression)expression,routeValues);

    //Support function
    private static string Action<T>(this UrlHelper helper,LambdaExpression expression,object routeValues = null) where T : Controller
        => helper.Action(
                ((MethodInfo)((ConstantExpression)((MethodCallExpression)((UnaryExpression)expression.Body).Operand).Object).Value).Name,
                typeof(T).Name.Replace("Controller","").Replace("controller",""),
                routeValues);
    }
}

지금까지 본 모든 솔루션에는 한 가지 단점이 있습니다. 즉, 컨트롤러 또는 작업 이름을 변경하는 것을 안전하게 하지만 두 엔티티 간의 일관성을 보장하지는 않습니다.다른 컨트롤러에서 작업을 지정할 수 있습니다.

public class HomeController : Controller
{
    public ActionResult HomeAction() { ... }
}

public class AnotherController : Controller
{
    public ActionResult AnotherAction() { ... }

    private void Process()
    {
        Url.Action(nameof(AnotherAction), nameof(HomeController));
    }
}

설상가상으로, 이 접근 방식은 컨트롤러에 적용할 수 있는 수많은 속성 및/또는 라우팅 변경 작업을 고려할 수 없습니다.RouteAttribute그리고.RoutePrefixAttribute속성 기반 라우팅에 대한 변경 사항이 무시될 수 있습니다.

막으로마지,로으,Url.Action() 변수 의 일관성을

public class HomeController : Controller
{
    public ActionResult HomeAction(int id, string name) { ... }

    private void Process()
    {
        Url.Action(nameof(HomeAction), new { identity = 1, title = "example" });
    }
}

내 솔루션은 다음을 기반으로 합니다.Expression 메타데이터 메타데이터:

public static class ActionHelper<T> where T : Controller
{
    public static string GetUrl(Expression<Func<T, Func<ActionResult>>> action)
    {
        return GetControllerName() + '/' + GetActionName(GetActionMethod(action));
    }

    public static string GetUrl<U>(
        Expression<Func<T, Func<U, ActionResult>>> action, U param)
    {
        var method = GetActionMethod(action);
        var parameters = method.GetParameters();

        return GetControllerName() + '/' + GetActionName(method) +
            '?' + GetParameter(parameters[0], param);
    }

    public static string GetUrl<U1, U2>(
        Expression<Func<T, Func<U1, U2, ActionResult>>> action, U1 param1, U2 param2)
    {
        var method = GetActionMethod(action);
        var parameters = method.GetParameters();

        return GetControllerName() + '/' + GetActionName(method) +
            '?' + GetParameter(parameters[0], param1) +
            '&' + GetParameter(parameters[1], param2);
    }

    private static string GetControllerName()
    {
        const string SUFFIX = nameof(Controller);
        string name = typeof(T).Name;
        return name.EndsWith(SUFFIX) ? name.Substring(0, name.Length - SUFFIX.Length) : name;
    }

    private static MethodInfo GetActionMethod(LambdaExpression expression)
    {
        var unaryExpr = (UnaryExpression)expression.Body;
        var methodCallExpr = (MethodCallExpression)unaryExpr.Operand;
        var methodCallObject = (ConstantExpression)methodCallExpr.Object;
        var method = (MethodInfo)methodCallObject.Value;

        Debug.Assert(method.IsPublic);
        return method;
    }

    private static string GetActionName(MethodInfo info)
    {
        return info.Name;
    }

    private static string GetParameter<U>(ParameterInfo info, U value)
    {
        return info.Name + '=' + Uri.EscapeDataString(value.ToString());
    }
}

이렇게 하면 잘못된 매개 변수를 전달하여 URL을 생성할 수 있습니다.

ActionHelper<HomeController>.GetUrl(controller => controller.HomeAction, 1, "example");

람다 식이므로 작업은 항상 해당 컨트롤러에 바인딩됩니다. (또한 IntelliSense도 사용할 수 있습니다.작업을 선택하면 올바른 유형의 모든 매개 변수를 지정해야 합니다.

지정된 코드는 여전히 라우팅 문제를 해결하지 못하지만 컨트롤러의 문제가 모두 있기 때문에 적어도 수정은 가능합니다.Type.Attributes그리고.MethodInfo.Attributes이용할 수 있는.

편집:

@CarterMedlin이 지적했듯이 원시 유형이 아닌 작업 매개 변수는 쿼리 매개 변수에 대한 일대일 바인딩을 갖지 않을 수 있습니다.현재 이 문제는 전화로 해결됩니다.ToString()매개 변수 클래스에서 재정의할 수 있습니다.그러나 접근 방식이 항상 적용되는 것은 아니며 매개 변수 이름을 제어하는 것도 아닙니다.

이 문제를 해결하려면 다음 인터페이스를 선언할 수 있습니다.

public interface IUrlSerializable
{
    Dictionary<string, string> GetQueryParams();
}

매개 변수 클래스에서 이를 구현합니다.

public class HomeController : Controller
{
    public ActionResult HomeAction(Model model) { ... }
}

public class Model : IUrlSerializable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public Dictionary<string, string> GetQueryParams()
    {
        return new Dictionary<string, string>
        {
            [nameof(Id)] = Id,
            [nameof(Name)] = Name
        };
    }
}

그고각변사항은경의각리▁to▁changesive은항에 대한 각각의 변화.ActionHelper:

public static class ActionHelper<T> where T : Controller
{
    ...

    private static string GetParameter<U>(ParameterInfo info, U value)
    {
        var serializableValue = value as IUrlSerializable;

        if (serializableValue == null)
            return GetParameter(info.Name, value.ToString());

        return String.Join("&",
            serializableValue.GetQueryParams().Select(param => GetParameter(param.Key, param.Value)));
    }

    private static string GetParameter(string name, string value)
    {
        return name + '=' + Uri.EscapeDataString(value);
    }
}

보다시피 여전히 다음과 같은 단점이 있습니다.ToString()매개 변수 클래스가 인터페이스를 구현하지 않는 경우.

용도:

ActionHelper<HomeController>.GetUrl(controller => controller.HomeAction, new Model
{
    Id = 1,
    Name = "example"
});

(컨트롤러에 유형 안전을 도입한) Gigi의 답변을 바탕으로, 저는 한 단계 더 나아갔습니다.저는 T4MVC를 매우 좋아하지만, T4 세대를 운영해야 하는 것은 결코 좋아하지 않았습니다.저는 코드 생성을 좋아하지만 MSBuild의 네이티브가 아니기 때문에 빌드 서버는 코드 생성에 어려움을 겪습니다.

저는 일반적인 개념을 재사용하고 다음에 추가했습니다.Expression매개변수:

public static class ControllerExtensions
{
    public static ActionResult RedirectToAction<TController>(
        this Controller controller, 
        Expression<Func<TController, ActionResult>> expression)
        where TController : Controller
    {
        var fullControllerName = typeof(TController).Name;
        var controllerName = fullControllerName.EndsWith("Controller")
            ? fullControllerName.Substring(0, fullControllerName.Length - 10)
            : fullControllerName;

        var actionCall = (MethodCallExpression) expression.Body;
        return controller.RedirectToAction(actionCall.Method.Name, controllerName);
    }
}

위의 호출 예는 다음과 같습니다.

    public virtual ActionResult Index()
    {
        return this.RedirectToAction<JobController>( controller => controller.Index() );
    }

한다면JobController가지고 있지 않은Index컴파일러 오류가 발생할 수 있습니다.그것이 아마도 이것이 이전 답변보다 가진 유일한 이점일 것입니다 - 그래서 그것은 또 다른 어리석음 검사입니다.사용을 중단하는 데 도움이 될 것입니다.JobController한다면JobController가지고 있지 않은Index또한 액션을 찾을 때 지능을 제공합니다.

--

또한 이 서명에 다음을 추가했습니다.

    public static ActionResult RedirectToAction<TController>(this TController controller, Expression<Func<TController, ActionResult>> expression)
        where TController : Controller

따라서 유형을 지정할 필요 없이 현재 컨트롤러에 대한 작업을 더 간단하게 입력할 수 있습니다.두 가지를 나란히 사용할 수 있습니다.

    public virtual ActionResult Index()
    {
        return this.RedirectToAction(controller => controller.Test());
    }
    public virtual ActionResult Test()
    {
         ...
    }

--

이것이 매개변수를 지원하는지 댓글로 질문받았습니다.위의 답변은 아니오입니다.하지만, 저는 매개 변수를 구문 분석할 수 있는 버전을 만들기 위해 매우 빠르게 해킹했습니다.조정된 방법은 다음과 같습니다.

    public static ActionResult RedirectToAction<TController>(this Controller controller, Expression<Func<TController, ActionResult>> expression)
        where TController : Controller
    {
        var fullControllerName = typeof(TController).Name;
        var controllerName = fullControllerName.EndsWith("Controller")
            ? fullControllerName.Substring(0, fullControllerName.Length - 10)
            : fullControllerName;

        var actionCall = (MethodCallExpression)expression.Body;

        var routeValues = new ExpandoObject();
        var routeValuesDictionary = (IDictionary<String, Object>)routeValues;
        var parameters = actionCall.Method.GetParameters();
        for (var i = 0; i < parameters.Length; i++)
        {
            var arugmentLambda = Expression.Lambda(actionCall.Arguments[i], expression.Parameters);
            var arugmentDelegate = arugmentLambda.Compile();
            var argumentValue = arugmentDelegate.DynamicInvoke(controller);
            routeValuesDictionary[parameters[i].Name] = argumentValue;
        }
        return controller.RedirectToAction(actionCall.Method.Name, controllerName, routeValues);
    }

저는 그것을 개인적으로 테스트하지 않았습니다(그러나 IntelliSense는 컴파일할 것처럼 보이게 합니다).요약하면 코드는 메서드에 대한 모든 매개 변수를 살펴보고 모든 매개 변수가 포함된 ExpandoObject를 만듭니다.값은 전달된 식에서 마스터 식의 원래 매개 변수를 사용하여 각각 독립 람다 식으로 호출하여 결정됩니다.그런 다음 식을 컴파일하고 호출한 다음 결과 값을 ExpandoObject에 저장합니다.그런 다음 결과는 기본 제공 도우미로 전달됩니다.

단순 상수는 작업을 수행하거나 원하는 경우 인터페이스를 수행합니다.

internal interface IBaseController { public string Name { get; } }
public class MyController : IBaseController
{
    public const string NAME = "My";
    // or
    public string Name { get => "My"; }
    ...
}

계산이 필요하지 않습니다.

<a asp-controller="@MyController.Name" asp-action="@nameof(MyController.Index)">Close</a>

@James에 대한 견해:

대신 문자열 확장 방법 사용: 컨트롤러 이름 접두사를 반환합니다. 그렇지 않으면 매개 변수가 전달되었습니다.

    /// <summary>
    /// Gets the prefix of the controller name.
    /// <para> <see langword="Usage:"/>
    /// <code>var <paramref name="controllerNamePrefix"/> = 
    /// <see langword="nameof"/>(ExampleController).
    /// <see cref="GetControllerPrefix()"/>;
    /// </code>
    /// </para>
    /// </summary>
    /// <param name="fullControllerName"></param>
    /// <returns></returns>
    public static string GetControllerPrefix(this string fullControllerName)
    {
        const string Controller = nameof(Controller);

        if (string.IsNullOrEmpty(fullControllerName) || !fullControllerName.EndsWith(Controller))
            return fullControllerName;

        return fullControllerName.Substring(0, fullControllerName.Length - Controller.Length);
    }

ASP에서 이 작업을 수행하는 방법을 찾고 있는 사람들을 위한 것입니다.NET Core에서 사용해 보십시오. https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting

@(Html.ActionLink<HomeController>("Home page", c => c.Index()))

저는 이런 것을 사용하는데 작동합니다: Url.작업((작업 이름), (홈 컨트롤러)의 이름입니다.대체("컨트롤러", 문자열).비어 있음)

언급URL : https://stackoverflow.com/questions/27444121/how-to-use-c-sharp-nameof-with-asp-net-mvc-url-action

반응형